Clean up block_ctxt representation

This commit is contained in:
Marijn Haverbeke 2012-02-17 11:18:14 +01:00
parent 54d7bffbb8
commit 9f4206cdc4
2 changed files with 131 additions and 140 deletions

View file

@ -2108,7 +2108,7 @@ fn trans_for(cx: @block_ctxt, local: @ast::local, seq: @ast::expr,
body: ast::blk, outer_next_cx: @block_ctxt) -> @block_ctxt { body: ast::blk, outer_next_cx: @block_ctxt) -> @block_ctxt {
let next_cx = new_sub_block_ctxt(bcx, "next"); let next_cx = new_sub_block_ctxt(bcx, "next");
let scope_cx = let scope_cx =
new_loop_scope_block_ctxt(bcx, option::some(next_cx), new_loop_scope_block_ctxt(bcx, cont_other(next_cx),
outer_next_cx, "for loop scope", outer_next_cx, "for loop scope",
body.span); body.span);
Br(bcx, scope_cx.llbb); Br(bcx, scope_cx.llbb);
@ -2138,7 +2138,7 @@ fn trans_for(cx: @block_ctxt, local: @ast::local, seq: @ast::expr,
fn trans_while(cx: @block_ctxt, cond: @ast::expr, body: ast::blk) fn trans_while(cx: @block_ctxt, cond: @ast::expr, body: ast::blk)
-> @block_ctxt { -> @block_ctxt {
let next_cx = new_sub_block_ctxt(cx, "while next"); let next_cx = new_sub_block_ctxt(cx, "while next");
let cond_cx = new_loop_scope_block_ctxt(cx, none, next_cx, let cond_cx = new_loop_scope_block_ctxt(cx, cont_self, next_cx,
"while cond", body.span); "while cond", body.span);
let body_cx = new_scope_block_ctxt(cond_cx, "while loop body"); let body_cx = new_scope_block_ctxt(cond_cx, "while loop body");
Br(cx, cond_cx.llbb); Br(cx, cond_cx.llbb);
@ -2154,7 +2154,7 @@ fn trans_do_while(cx: @block_ctxt, body: ast::blk, cond: @ast::expr) ->
@block_ctxt { @block_ctxt {
let next_cx = new_sub_block_ctxt(cx, "next"); let next_cx = new_sub_block_ctxt(cx, "next");
let body_cx = let body_cx =
new_loop_scope_block_ctxt(cx, option::none::<@block_ctxt>, next_cx, new_loop_scope_block_ctxt(cx, cont_self, next_cx,
"do-while loop body", body.span); "do-while loop body", body.span);
let body_end = trans_block(body_cx, body, ignore); let body_end = trans_block(body_cx, body, ignore);
let cond_cx = new_scope_block_ctxt(body_cx, "do-while cond"); let cond_cx = new_scope_block_ctxt(body_cx, "do-while cond");
@ -2995,27 +2995,32 @@ fn invoke_(bcx: @block_ctxt, llfn: ValueRef, llargs: [ValueRef],
} }
fn get_landing_pad(bcx: @block_ctxt) -> BasicBlockRef { fn get_landing_pad(bcx: @block_ctxt) -> BasicBlockRef {
fn find_scope_for_lpad(bcx: @block_ctxt) -> @block_ctxt { fn in_lpad_scope_cx(bcx: @block_ctxt, f: fn(scope_info)) {
let scope_bcx = bcx; let bcx = bcx;
while true { while true {
if vec::is_not_empty(scope_bcx.cleanups) { break; } alt bcx.kind {
scope_bcx = alt scope_bcx.parent { scope_block(info) {
parent_some(b) { b } if info.cleanups.len() > 0u || bcx.parent == parent_none {
parent_none { break; } f(info); ret;
}; }
}
_ {}
}
bcx = alt check bcx.parent { parent_some(b) { b } };
} }
scope_bcx
} }
let scope_bcx = find_scope_for_lpad(bcx); let cached = none, pad_bcx = bcx; // Guaranteed to be set below
in_lpad_scope_cx(bcx) {|info|
// If there is a valid landing pad still around, use it // If there is a valid landing pad still around, use it
alt scope_bcx.landing_pad { alt info.landing_pad {
some(target) { ret target; } some(target) { cached = some(target); ret; }
none {} none {}
} }
pad_bcx = new_sub_block_ctxt(bcx, "unwind");
let pad_bcx = new_sub_block_ctxt(bcx, "unwind"); info.landing_pad = some(pad_bcx.llbb);
scope_bcx.landing_pad = some(pad_bcx.llbb); }
alt cached { some(b) { ret b; } none {} } // Can't return from block above
// The landing pad return type (the type being propagated). Not sure what // The landing pad return type (the type being propagated). Not sure what
// this represents but it's determined by the personality function and // this represents but it's determined by the personality function and
// this is what the EH proposal example uses. // this is what the EH proposal example uses.
@ -3573,13 +3578,13 @@ fn trans_break_cont(bcx: @block_ctxt, to_end: bool)
let unwind = bcx, target = bcx; let unwind = bcx, target = bcx;
while true { while true {
alt unwind.kind { alt unwind.kind {
LOOP_SCOPE_BLOCK(_cont, _break) { scope_block({is_loop: some({cnt, brk}), _}) {
target = if to_end { target = if to_end {
_break brk
} else { } else {
alt _cont { alt cnt {
option::some(_cont) { _cont } cont_other(o) { o }
_ { unwind } cont_self { unwind }
} }
}; };
break; break;
@ -3738,16 +3743,14 @@ fn new_block_ctxt(cx: @fn_ctxt, parent: block_parent, kind: block_kind,
if cx.ccx.sess.opts.save_temps || cx.ccx.sess.opts.debuginfo { if cx.ccx.sess.opts.save_temps || cx.ccx.sess.opts.debuginfo {
s = cx.ccx.names(name); s = cx.ccx.names(name);
} }
let llbb: BasicBlockRef = let llbb: BasicBlockRef = str::as_buf(s, {|buf|
str::as_buf(s, {|buf| llvm::LLVMAppendBasicBlock(cx.llfn, buf) }); llvm::LLVMAppendBasicBlock(cx.llfn, buf)
});
let bcx = @{llbb: llbb, let bcx = @{llbb: llbb,
mutable terminated: false, mutable terminated: false,
mutable unreachable: false, mutable unreachable: false,
parent: parent, parent: parent,
kind: kind, kind: kind,
mutable cleanups: [],
mutable cleanup_paths: [],
mutable landing_pad: none,
block_span: block_span, block_span: block_span,
fcx: cx}; fcx: cx};
alt parent { alt parent {
@ -3759,34 +3762,43 @@ fn new_block_ctxt(cx: @fn_ctxt, parent: block_parent, kind: block_kind,
ret bcx; ret bcx;
} }
fn simple_scope_block() -> block_kind {
scope_block({is_loop: none, mutable cleanups: [],
mutable cleanup_paths: [], mutable landing_pad: none})
}
// Use this when you're at the top block of a function or the like. // Use this when you're at the top block of a function or the like.
fn new_top_block_ctxt(fcx: @fn_ctxt, sp: option<span>) -> @block_ctxt { fn new_top_block_ctxt(fcx: @fn_ctxt, sp: option<span>) -> @block_ctxt {
ret new_block_ctxt(fcx, parent_none, SCOPE_BLOCK, "function top level", ret new_block_ctxt(fcx, parent_none, simple_scope_block(),
sp); "function top level", sp);
} }
// Use this when you're at a curly-brace or similar lexical scope. // Use this when you're at a curly-brace or similar lexical scope.
fn new_scope_block_ctxt(bcx: @block_ctxt, n: str) -> @block_ctxt { fn new_scope_block_ctxt(bcx: @block_ctxt, n: str) -> @block_ctxt {
ret new_block_ctxt(bcx.fcx, parent_some(bcx), SCOPE_BLOCK, n, none); ret new_block_ctxt(bcx.fcx, parent_some(bcx), simple_scope_block(),
n, none);
} }
fn new_real_block_ctxt(bcx: @block_ctxt, n: str, sp: span) -> @block_ctxt { fn new_real_block_ctxt(bcx: @block_ctxt, n: str, sp: span) -> @block_ctxt {
ret new_block_ctxt(bcx.fcx, parent_some(bcx), SCOPE_BLOCK, n, some(sp)); ret new_block_ctxt(bcx.fcx, parent_some(bcx), simple_scope_block(),
n, some(sp));
} }
fn new_loop_scope_block_ctxt(bcx: @block_ctxt, _cont: option<@block_ctxt>, fn new_loop_scope_block_ctxt(bcx: @block_ctxt, _cont: loop_cont,
_break: @block_ctxt, n: str, sp: span) _break: @block_ctxt, n: str, sp: span)
-> @block_ctxt { -> @block_ctxt {
ret new_block_ctxt(bcx.fcx, parent_some(bcx), ret new_block_ctxt(bcx.fcx, parent_some(bcx), scope_block({
LOOP_SCOPE_BLOCK(_cont, _break), n, some(sp)); is_loop: some({cnt: _cont, brk: _break}),
mutable cleanups: [],
mutable cleanup_paths: [],
mutable landing_pad: none
}), n, some(sp));
} }
// Use this when you're making a general CFG BB within a scope. // Use this when you're making a general CFG BB within a scope.
fn new_sub_block_ctxt(bcx: @block_ctxt, n: str) -> @block_ctxt { fn new_sub_block_ctxt(bcx: @block_ctxt, n: str) -> @block_ctxt {
ret new_block_ctxt(bcx.fcx, parent_some(bcx), NON_SCOPE_BLOCK, n, none); ret new_block_ctxt(bcx.fcx, parent_some(bcx), non_scope_block, n, none);
} }
fn new_raw_block_ctxt(fcx: @fn_ctxt, llbb: BasicBlockRef) -> @block_ctxt { fn new_raw_block_ctxt(fcx: @fn_ctxt, llbb: BasicBlockRef) -> @block_ctxt {
@ -3794,10 +3806,7 @@ fn new_raw_block_ctxt(fcx: @fn_ctxt, llbb: BasicBlockRef) -> @block_ctxt {
mutable terminated: false, mutable terminated: false,
mutable unreachable: false, mutable unreachable: false,
parent: parent_none, parent: parent_none,
kind: NON_SCOPE_BLOCK, kind: non_scope_block,
mutable cleanups: [],
mutable cleanup_paths: [],
mutable landing_pad: none,
block_span: none, block_span: none,
fcx: fcx}; fcx: fcx};
} }
@ -3813,12 +3822,12 @@ fn new_raw_block_ctxt(fcx: @fn_ctxt, llbb: BasicBlockRef) -> @block_ctxt {
fn trans_block_cleanups(bcx: @block_ctxt, cleanup_cx: @block_ctxt) -> fn trans_block_cleanups(bcx: @block_ctxt, cleanup_cx: @block_ctxt) ->
@block_ctxt { @block_ctxt {
if bcx.unreachable { ret bcx; } if bcx.unreachable { ret bcx; }
let i = cleanup_cx.cleanups.len(), bcx = bcx; let bcx = bcx;
if cleanup_cx.kind == NON_SCOPE_BLOCK { assert i == 0u; } alt check cleanup_cx.kind {
while i > 0u { scope_block({cleanups, _}) {
i -= 1u; vec::riter(cleanups) {|cu|
alt cleanup_cx.cleanups[i] { alt cu { clean(cfn) | clean_temp(_, cfn) { bcx = cfn(bcx); } }
clean(cfn) | clean_temp(_, cfn) { bcx = cfn(bcx); } }
} }
} }
ret bcx; ret bcx;
@ -3831,9 +3840,9 @@ fn cleanup_and_leave(bcx: @block_ctxt, upto: option<BasicBlockRef>,
leave: option<BasicBlockRef>) { leave: option<BasicBlockRef>) {
let cur = bcx, bcx = bcx; let cur = bcx, bcx = bcx;
while true { while true {
if cur.cleanups.len() > 0u { alt cur.kind {
assert cur.kind != NON_SCOPE_BLOCK; scope_block(info) if info.cleanups.len() > 0u {
for exists in cur.cleanup_paths { for exists in info.cleanup_paths {
if exists.target == leave { if exists.target == leave {
Br(bcx, exists.dest); Br(bcx, exists.dest);
ret; ret;
@ -3841,9 +3850,11 @@ fn cleanup_and_leave(bcx: @block_ctxt, upto: option<BasicBlockRef>,
} }
let sub_cx = new_sub_block_ctxt(bcx, "cleanup"); let sub_cx = new_sub_block_ctxt(bcx, "cleanup");
Br(bcx, sub_cx.llbb); Br(bcx, sub_cx.llbb);
cur.cleanup_paths += [{target: leave, dest: sub_cx.llbb}]; info.cleanup_paths += [{target: leave, dest: sub_cx.llbb}];
bcx = trans_block_cleanups(sub_cx, cur); bcx = trans_block_cleanups(sub_cx, cur);
} }
_ {}
}
alt upto { alt upto {
some(bb) { if cur.llbb == bb { break; } } some(bb) { if cur.llbb == bb { break; } }
_ {} _ {}
@ -3890,33 +3901,6 @@ fn block_locals(b: ast::blk, it: fn(@ast::local)) {
} }
} }
fn llstaticallocas_block_ctxt(fcx: @fn_ctxt) -> @block_ctxt {
ret @{llbb: fcx.llstaticallocas,
mutable terminated: false,
mutable unreachable: false,
parent: parent_none,
kind: SCOPE_BLOCK,
mutable cleanups: [],
mutable cleanup_paths: [],
mutable landing_pad: none,
block_span: none,
fcx: fcx};
}
fn llderivedtydescs_block_ctxt(fcx: @fn_ctxt) -> @block_ctxt {
ret @{llbb: fcx.llderivedtydescs,
mutable terminated: false,
mutable unreachable: false,
parent: parent_none,
kind: SCOPE_BLOCK,
mutable cleanups: [],
mutable cleanup_paths: [],
mutable landing_pad: none,
block_span: none,
fcx: fcx};
}
fn alloc_ty(cx: @block_ctxt, t: ty::t) -> result { fn alloc_ty(cx: @block_ctxt, t: ty::t) -> result {
let bcx = cx, ccx = bcx_ccx(cx); let bcx = cx, ccx = bcx_ccx(cx);
let llty = type_of(ccx, t); let llty = type_of(ccx, t);
@ -3927,7 +3911,8 @@ fn alloc_ty(cx: @block_ctxt, t: ty::t) -> result {
// block_ctxt built on the llderivedtydescs block for the fn, // block_ctxt built on the llderivedtydescs block for the fn,
// so that the size dominates the array_alloca that // so that the size dominates the array_alloca that
// comes next. // comes next.
let n = size_of(llderivedtydescs_block_ctxt(bcx.fcx), t); let n = size_of(new_raw_block_ctxt(cx.fcx, cx.fcx.llderivedtydescs),
t);
bcx.fcx.llderivedtydescs = n.bcx.llbb; bcx.fcx.llderivedtydescs = n.bcx.llbb;
PointerCast(bcx, dynastack_alloca(bcx, T_i8(), n.val, t), T_ptr(llty)) PointerCast(bcx, dynastack_alloca(bcx, T_i8(), n.val, t), T_ptr(llty))
}; };

View file

@ -233,16 +233,17 @@ enum cleanup {
type cleanup_path = {target: option<BasicBlockRef>, type cleanup_path = {target: option<BasicBlockRef>,
dest: BasicBlockRef}; dest: BasicBlockRef};
fn scope_clean_changed(cx: @block_ctxt) { fn scope_clean_changed(info: scope_info) {
cx.cleanup_paths = []; if info.cleanup_paths.len() > 0u { info.cleanup_paths = []; }
cx.landing_pad = none; info.landing_pad = none;
} }
fn add_clean(cx: @block_ctxt, val: ValueRef, ty: ty::t) { fn add_clean(cx: @block_ctxt, val: ValueRef, ty: ty::t) {
if !ty::type_needs_drop(bcx_tcx(cx), ty) { ret; } if !ty::type_needs_drop(bcx_tcx(cx), ty) { ret; }
let scope_cx = find_scope_cx(cx); in_scope_cx(cx) {|info|
scope_cx.cleanups += [clean(bind drop_ty(_, val, ty))]; info.cleanups += [clean(bind drop_ty(_, val, ty))];
scope_clean_changed(scope_cx); scope_clean_changed(info);
}
} }
fn add_clean_temp(cx: @block_ctxt, val: ValueRef, ty: ty::t) { fn add_clean_temp(cx: @block_ctxt, val: ValueRef, ty: ty::t) {
if !ty::type_needs_drop(bcx_tcx(cx), ty) { ret; } if !ty::type_needs_drop(bcx_tcx(cx), ty) { ret; }
@ -254,23 +255,25 @@ fn add_clean_temp(cx: @block_ctxt, val: ValueRef, ty: ty::t) {
ret drop_ty(bcx, val, ty); ret drop_ty(bcx, val, ty);
} }
} }
let scope_cx = find_scope_cx(cx); in_scope_cx(cx) {|info|
scope_cx.cleanups += info.cleanups += [clean_temp(val, bind do_drop(_, val, ty))];
[clean_temp(val, bind do_drop(_, val, ty))]; scope_clean_changed(info);
scope_clean_changed(scope_cx); }
} }
fn add_clean_temp_mem(cx: @block_ctxt, val: ValueRef, ty: ty::t) { fn add_clean_temp_mem(cx: @block_ctxt, val: ValueRef, ty: ty::t) {
if !ty::type_needs_drop(bcx_tcx(cx), ty) { ret; } if !ty::type_needs_drop(bcx_tcx(cx), ty) { ret; }
let scope_cx = find_scope_cx(cx); in_scope_cx(cx) {|info|
scope_cx.cleanups += [clean_temp(val, bind drop_ty(_, val, ty))]; info.cleanups += [clean_temp(val, bind drop_ty(_, val, ty))];
scope_clean_changed(scope_cx); scope_clean_changed(info);
}
} }
fn add_clean_free(cx: @block_ctxt, ptr: ValueRef, shared: bool) { fn add_clean_free(cx: @block_ctxt, ptr: ValueRef, shared: bool) {
let scope_cx = find_scope_cx(cx);
let free_fn = if shared { bind base::trans_shared_free(_, ptr) } let free_fn = if shared { bind base::trans_shared_free(_, ptr) }
else { bind base::trans_free(_, ptr) }; else { bind base::trans_free(_, ptr) };
scope_cx.cleanups += [clean_temp(ptr, free_fn)]; in_scope_cx(cx) {|info|
scope_clean_changed(scope_cx); info.cleanups += [clean_temp(ptr, free_fn)];
scope_clean_changed(info);
}
} }
// Note that this only works for temporaries. We should, at some point, move // Note that this only works for temporaries. We should, at some point, move
@ -278,27 +281,22 @@ fn add_clean_free(cx: @block_ctxt, ptr: ValueRef, shared: bool) {
// this will be more involved. For now, we simply zero out the local, and the // this will be more involved. For now, we simply zero out the local, and the
// drop glue checks whether it is zero. // drop glue checks whether it is zero.
fn revoke_clean(cx: @block_ctxt, val: ValueRef) { fn revoke_clean(cx: @block_ctxt, val: ValueRef) {
let sc_cx = find_scope_cx(cx); in_scope_cx(cx) {|info|
let found = -1; let i = 0u;
let i = 0; for cu in info.cleanups {
for c: cleanup in sc_cx.cleanups { alt cu {
alt c { clean_temp(v, _) if v == val {
clean_temp(v, _) { info.cleanups =
if v as uint == val as uint { found = i; break; } vec::slice(info.cleanups, 0u, i) +
vec::slice(info.cleanups, i + 1u, info.cleanups.len());
scope_clean_changed(info);
ret;
} }
_ {} _ {}
} }
i += 1; i += 1u;
}
} }
// The value does not have a cleanup associated with it.
if found == -1 { ret; }
// We found the cleanup and remove it
sc_cx.cleanups =
vec::slice(sc_cx.cleanups, 0u, found as uint) +
vec::slice(sc_cx.cleanups, (found as uint) + 1u,
sc_cx.cleanups.len());
scope_clean_changed(sc_cx);
ret;
} }
fn get_res_dtor(ccx: @crate_ctxt, did: ast::def_id, inner_t: ty::t) fn get_res_dtor(ccx: @crate_ctxt, did: ast::def_id, inner_t: ty::t)
@ -325,47 +323,53 @@ enum block_kind {
// cleaned up. May correspond to an actual block in the language, but also // cleaned up. May correspond to an actual block in the language, but also
// to an implicit scope, for example, calls introduce an implicit scope in // to an implicit scope, for example, calls introduce an implicit scope in
// which the arguments are evaluated and cleaned up. // which the arguments are evaluated and cleaned up.
SCOPE_BLOCK, scope_block(scope_info),
// A basic block created from the body of a loop. Contains pointers to
// which block to jump to in the case of "continue" or "break".
LOOP_SCOPE_BLOCK(option<@block_ctxt>, @block_ctxt),
// A non-scope block is a basic block created as a translation artifact // A non-scope block is a basic block created as a translation artifact
// from translating code that expresses conditional logic rather than by // from translating code that expresses conditional logic rather than by
// explicit { ... } block structure in the source language. It's called a // explicit { ... } block structure in the source language. It's called a
// non-scope block because it doesn't introduce a new variable scope. // non-scope block because it doesn't introduce a new variable scope.
NON_SCOPE_BLOCK non_scope_block,
} }
enum loop_cont { cont_self, cont_other(@block_ctxt), }
type scope_info = {
is_loop: option<{cnt: loop_cont, brk: @block_ctxt}>,
// A list of functions that must be run at when leaving this
// block, cleaning up any variables that were introduced in the
// block.
mutable cleanups: [cleanup],
// Existing cleanup paths that may be reused, indexed by destination and
// cleared when the set of cleanups changes.
mutable cleanup_paths: [cleanup_path],
// Unwinding landing pad. Also cleared when cleanups change.
mutable landing_pad: option<BasicBlockRef>,
};
// Basic block context. We create a block context for each basic block // Basic block context. We create a block context for each basic block
// (single-entry, single-exit sequence of instructions) we generate from Rust // (single-entry, single-exit sequence of instructions) we generate from Rust
// code. Each basic block we generate is attached to a function, typically // code. Each basic block we generate is attached to a function, typically
// with many basic blocks per function. All the basic blocks attached to a // with many basic blocks per function. All the basic blocks attached to a
// function are organized as a directed graph. // function are organized as a directed graph.
type block_ctxt = type block_ctxt = {
// The BasicBlockRef returned from a call to // The BasicBlockRef returned from a call to
// llvm::LLVMAppendBasicBlock(llfn, name), which adds a basic // llvm::LLVMAppendBasicBlock(llfn, name), which adds a basic
// block to the function pointed to by llfn. We insert // block to the function pointed to by llfn. We insert
// instructions into that block by way of this block context. // instructions into that block by way of this block context.
// The block pointing to this one in the function's digraph. // The block pointing to this one in the function's digraph.
// The 'kind' of basic block this is. llbb: BasicBlockRef,
// A list of functions that run at the end of translating this
// block, cleaning up any variables that were introduced in the
// block and need to go out of scope at the end of it.
// The source span where this block comes from, for error
// reporting. FIXME this is not currently reliable
// The function context for the function to which this block is
// attached.
{llbb: BasicBlockRef,
mutable terminated: bool, mutable terminated: bool,
mutable unreachable: bool, mutable unreachable: bool,
parent: block_parent, parent: block_parent,
// The 'kind' of basic block this is.
kind: block_kind, kind: block_kind,
// FIXME the next five fields should probably only appear in scope blocks // The source span where the block came from, if it is a block that
mutable cleanups: [cleanup], // actually appears in the source code.
mutable cleanup_paths: [cleanup_path],
mutable landing_pad: option<BasicBlockRef>,
block_span: option<span>, block_span: option<span>,
fcx: @fn_ctxt}; // The function context for the function to which this block is
// attached.
fcx: @fn_ctxt
};
// FIXME: we should be able to use option<@block_parent> here but // FIXME: we should be able to use option<@block_parent> here but
// the infinite-enum check in rustboot gets upset. // the infinite-enum check in rustboot gets upset.
@ -395,13 +399,15 @@ fn struct_elt(llstructty: TypeRef, n: uint) -> TypeRef unsafe {
ret llvm::LLVMGetElementType(elt_tys[n]); ret llvm::LLVMGetElementType(elt_tys[n]);
} }
fn find_scope_cx(cx: @block_ctxt) -> @block_ctxt { fn in_scope_cx(cx: @block_ctxt, f: fn(scope_info)) {
let cur = cx; let cur = cx;
while true { while true {
if cur.kind != NON_SCOPE_BLOCK { break; } alt cur.kind {
scope_block(info) { f(info); ret; }
_ {}
}
cur = alt check cur.parent { parent_some(b) { b } }; cur = alt check cur.parent { parent_some(b) { b } };
} }
cur
} }
// Accessors // Accessors