Skip to content

Commit

Permalink
Increment references when returning variables
Browse files Browse the repository at this point in the history
When a scope or method returns a local variable that stores a reference,
we now return a new reference and leave the existing register state
as-is. In some cases this may lead to redundant reference counting
increments, but we should be able to optimize those away in the future
(something we need to do anyway).

This fixes #670.

Changelog: fixed
  • Loading branch information
yorickpeterse committed Dec 15, 2023
1 parent c9f9bc5 commit c709b59
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 9 deletions.
23 changes: 14 additions & 9 deletions compiler/src/mir/passes.rs
Expand Up @@ -156,8 +156,13 @@ impl RegisterKind {
matches!(self, RegisterKind::Field(_))
}

pub(crate) fn is_field_or_self(self) -> bool {
matches!(self, RegisterKind::Field(_) | RegisterKind::SelfObject)
pub(crate) fn new_reference_on_return(self) -> bool {
matches!(
self,
RegisterKind::Field(_)
| RegisterKind::SelfObject
| RegisterKind::Variable(_, _)
)
}

pub(crate) fn is_regular(self) -> bool {
Expand Down Expand Up @@ -1379,10 +1384,6 @@ impl<'a> LowerMethod<'a> {
}

let reg = if index == max_index {
// A body may capture an outer value type and return that. In
// this case we need to clone the value type, as the original
// value may still be in use after the body, hence the clone
// argument is set to `true`.
self.output_expression(n)
} else {
self.expression(n)
Expand Down Expand Up @@ -3557,9 +3558,13 @@ impl<'a> LowerMethod<'a> {
return reg;
}

// When returning/throwing a field or `self` as a reference, we must
// return a new reference.
if self.register_kind(reg).is_field_or_self() {
// When returning `self`, a reference to a field, or a local variable
// that stores a reference, we return a new reference. This is needed
// because for the first two cases we don't create references
// immediately as that's redundant. It's needed in the last case so we
// don't mark variables storing references as moved, preventing them
// from being used afterwards.
if self.register_kind(reg).new_reference_on_return() {
let res = self.new_register(typ);

self.current_block_mut().reference(res, reg, loc);
Expand Down
25 changes: 25 additions & 0 deletions std/test/diagnostics/scope_move.inko
@@ -0,0 +1,25 @@
fn example1 {
let a = [10]
let b = mut a

{
if true { mut a } else { b }
}

b # This is fine because `b` is a _reference_.
}

fn example2 {
let a = [10]
let b = [20]

{
if true { a } else { b }
}

a
b
}

# scope_move.inko:20:3 error(moved): 'a' can't be used as it has been moved
# scope_move.inko:21:3 error(moved): 'b' can't be used as it has been moved

0 comments on commit c709b59

Please sign in to comment.