pub struct Env { /* private fields */ }Expand description
A lexical environment: a persistent chain of frames, so a closure can capture cheaply.
Implementations§
Source§impl Env
impl Env
pub fn new() -> Env
pub fn extend(&self, bindings: Vec<(VarId, Value)>) -> Env
Sourcepub fn call_frame(
parent: &Arc<Env>,
params: &[VarId],
args: impl Iterator<Item = Value>,
locals: u32,
) -> Env
pub fn call_frame( parent: &Arc<Env>, params: &[VarId], args: impl Iterator<Item = Value>, locals: u32, ) -> Env
The frame a call runs in: the parameters, then locals reserved slots for the bindings the
body is going to make.
One allocation. Map<Range, _> has a length the compiler can trust, so collecting it into
an Arc<[_]> sizes the allocation once — which is why the parameters and the reserved tail
are produced by one iterator rather than a vector that is then converted.
Sourcepub fn put(&mut self, bindings: &mut Vec<(VarId, Value)>) -> bool
pub fn put(&mut self, bindings: &mut Vec<(VarId, Value)>) -> bool
Bind bindings into this frame’s reserved tail, if there is room for all of them and
nobody else is holding the frame.
Answers whether it did. false means the caller must fall back to Env::extend and
chain a scope — which happens when a closure has captured this environment (its clone holds
the frame, so Arc::get_mut refuses), when the reservation was too small, or when the
program was built by something that never ran the reservation pass at all.
The safety argument is the refusal: a closure that captured this environment can see the
slots this would write, and Arc::get_mut is what proves that has not happened. Every
binding gets a slot of its own, so nothing a closure captured is ever overwritten.
Sourcepub fn put_one(&mut self, var: VarId, value: Value) -> Result<(), Value>
pub fn put_one(&mut self, var: VarId, value: Value) -> Result<(), Value>
Env::put for a single binding, which is what a let is — and a let is much the most
common of the two, so it does not build a vector to hand over. Answers the value back when
there is no room for it.
Extend a parent that is already behind an Arc — which a closure’s captured environment
is, so that a call clones a pointer instead of boxing a copy of the environment.
This is the per-call path. extend above is the per-let path, where the parent is owned by
the evaluator’s loop and has to be boxed; there are far fewer of those.
An environment with nothing in it, behind an Arc, shared by every top-level definition.
pub fn get(&self, v: VarId) -> Option<&Value>
Sourcepub fn read(&mut self, v: VarId, may_move: bool) -> Option<Value>
pub fn read(&mut self, v: VarId, may_move: bool) -> Option<Value>
Read v, and move it out of the frame when three things hold: the caller says no later
evaluation reads it, this environment is the only holder of the frame it lives in, and the
value is one whose copy costs something.
The third condition is not an optimisation of an optimisation — it is what keeps the other
two from costing more than they save. Moving is strictly more work than cloning at the point
of the read: a clone of an Int is a copy of eight bytes and a clone of a container is one
atomic increment, where a move has to find the slot, prove the frame is unshared and empty
it. It pays only when somebody downstream can then use the sole ownership — which today is
list_append pushing in place and with rebuilding a record’s fields — and measuring it
without this condition showed every benchmark in the tree 6–13% slower, because the reads
that dominate a real program are of Ints and nothing was gained by moving one
(69 §69.7).
The caller must have established that no later evaluation reads v — crate::liveness
is what establishes it, and last_use is the flag. What this adds is the second half of the
safety argument: a frame is emptied only when nothing else holds it, so an environment
captured by a closure or shared with an inner scope is read from rather than emptied, and a
caller that is wrong about liveness gets an unbound-variable error rather than somebody
else’s missing binding.