Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ The generated loop is now written from the program's source alone, and the manif
- **A missing `depends` edge is reported as one.** Naming `Module.Type` from a module this module does not depend on used to advise adding the type to the other module's `exposes`.
- **A module checked or verified as one unit of a program is lowered against the program's answer modules**, not only those its own dependency cone reaches.
- **A generated loop updates an answer module's state in place on the Rust backend.** The loop now hands the state out of the run for the length of one answer, and every function on the way to the answer takes the run by value, so a Map or Vector the state holds is no longer copied whole on every request. A process answering 2,000 requests from a module holding a 100,000-entry Map went from 0.76 s to 0.01 s.
- **A record gives up its fields at its last use on the Rust backend.** `f(s.window.created, s.window.spent)`, where nothing reads that part of `s` afterwards, moves both Maps into `f` instead of cloning them, so `f` updates them in place instead of copying each one whole; a function that does this takes its record by value. `T.update(s, window = v)` at the last use of `s` drops the old `window` at once, not when the function returns, so a helper function that only empties a field is no longer needed to let it go. Moving 1,000 times two 100,000-entry Maps out of a record into a function that adds a key to each went from 0.77 s to 0.01 s; emptying and refilling such a window 10 times at a million entries peaked at 202 MiB instead of 334 MiB.
- **`aver check .` checks a process that calls an operation of a capability under a subdirectory.** An answer module walked as the entry of its own program lent the batch its bare `module` name, which no program can import, and every process lost the capability's namespace (`unknown-ident 'Lib'`).
- **`aver effects --write` adds the in-place effects a process is missing.** A yielding function's list is still left alone for what it reaches through its stops, but what its body performs where it is written is added, as `aver check` requires.
- **A wasm-gc build no longer asks for equality on an answer module's state.** The list of answer tuples every tuple gets in case `List.zip` builds one demanded an equality helper for a state that has none (one holding a provider's resource, for example), and the build failed with `carrier eq inner type ... has no eq dispatch`.
Expand Down
205 changes: 168 additions & 37 deletions src/codegen/rust/from_mir.rs

Large diffs are not rendered by default.

29 changes: 25 additions & 4 deletions src/codegen/rust/ownership.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,14 @@ pub(super) fn value_facts(expr: &MirExpr, ctx: &MirEmitCtx<'_>) -> RustValueFact
},
borrow_shape: BorrowShape::Direct,
// A field of a fresh temporary can move. A local-rooted
// projection stays conservative even when the root is at its
// final use; proving partial moves belongs in a later MIR pass.
can_move: copy || projection_root_local(&project.node.base.node).is_none(),
// projection moves only where `field_moves` proved no later or
// still-borrowed read overlaps it and the root is an owned
// Rust value.
can_move: copy
|| match projection_root_local(&project.node.base.node) {
None => true,
Some(root) => projection_moves(expr, root, ctx),
},
provider_resource: false,
};
}
Expand Down Expand Up @@ -231,7 +236,23 @@ fn clone_borrowed(code: String, shape: BorrowShape) -> String {
}
}

fn projection_root_local(expr: &MirExpr) -> Option<&MirLocal> {
/// Whether this field read may move its field out of `root`: the read is a
/// movable projection and `root` is an owned Rust value (not a borrowed
/// or wrapped parameter, and not carried unchanged into the next loop
/// iteration).
pub(super) fn projection_moves(expr: &MirExpr, root: &MirLocal, ctx: &MirEmitCtx<'_>) -> bool {
ctx.movable_projections
.contains(&(expr as *const MirExpr as usize))
&& root_is_owned(root, ctx)
}

/// Whether `local` is an owned Rust value that may give up its fields.
pub(super) fn root_is_owned(local: &MirLocal, ctx: &MirEmitCtx<'_>) -> bool {
local_value_facts(local, ctx).mode == RustValueMode::Owned
&& !ctx.loop_carried_params.contains(local.name.as_str())
}

pub(super) fn projection_root_local(expr: &MirExpr) -> Option<&MirLocal> {
match expr {
MirExpr::Local(_) => local_of(expr),
MirExpr::Project(project) => projection_root_local(&project.node.base.node),
Expand Down
2 changes: 1 addition & 1 deletion src/ir/mir/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ pub enum MirExpr {
///
/// The exhaustive match makes adding a `MirExpr` variant a compile error here,
/// keeping read-only MIR traversals on one canonical child enumeration.
pub(crate) fn walk_children(e: &MirExpr, f: &mut dyn FnMut(&MirExpr)) {
pub(crate) fn walk_children<'a>(e: &'a MirExpr, f: &mut dyn FnMut(&'a MirExpr)) {
match e {
MirExpr::Literal(_) | MirExpr::Local(_) | MirExpr::FnValue(_) => {}
MirExpr::Let(l) => {
Expand Down
Loading
Loading