Escape Analysis & Owner Escape Analysis

Richard Andrew Cattermole (Rikki) richard at cattermole.co.nz
Sat Aug 24 12:20:13 UTC 2024


As a follow-up to the recent DIP1000 meeting where it was agreed 
to start compiling a list of its failings and that inference is 
important to such a design, I am also posting my proposal for a 
complete replacement.

To those who have tried DIP1000 and then dumped it, I am 
interested to know how you find the owner escape analysis of this 
proposal in terms of being restrictiveness.
Please evaluate it, so I can know if there is a pattern that 
needs resolving (if possible).

Latest: 
https://gist.github.com/rikkimax/0c1de705bf6d9dbc1869d60baee0fa81

Current: 
https://gist.github.com/rikkimax/0c1de705bf6d9dbc1869d60baee0fa81/c369cb4e9416298c6cf348915205959ee272a5f8

This proposal is an attempted potential replacement for both 
DIP1000 and @live.
It is in recognition that we may not be able to get DIP1000 and 
@live fully functional in making memory that is borrowed from 
owners tracked within ``@safe`` code.
To do this, an escape set is described per parameter to describe 
the relationship of an input to its output.
An output is one that is tied to one or more inputs, and an input 
is any pointer that is stored in some place.

I do want to emphasize at this point that the escape set should 
be more or less be perfect in terms of inference due to inference 
as being a side effect of the verification. Rather than a 
separate process.
Unless you go virtual, or lack a body the need to annotate should 
be minimal.
A nice side effect of this, is that the compiler will be able to 
promote memory to the stack without you annotating ``scope``.

Escape analysis provides guarantees to its caller on the 
relationship to outputs for each function parameter. It is 
cross-scope aware.
Owner escape analysis provides guarantees to the callee that the 
inputs for each output will remain valid for the life of the 
output value.
They are complimentary of each other, enabling each to be simpler.

Here is a reference counted type example, although this can 
equally apply to any other pointer type:

```d
struct RC {
     int* borrow() @escapevia(return);
}

RC first(/* @escapevia(return) */ RC input) {
     int* borrowed1 = input.borrow();
     // input is an owner, and therefore protected due to the 
borrow borrowed1
     input = RC.init; // Error

     int* borrowed2 = second(borrowed1);
     // borrowed1 is an owner, and therefore protected due to the 
borrow borrowed2
     borrowed1 = null; // Error

     return input;
}

int* second(/* @escapevia(return) */ int* second) {
     writeln(*second);
     return second;
}
```

How it interacts with const:

```d
struct S {
       int field;

@safe:

     bool isNull() const {
         return false;
     }

     void makeNull() {
     }
}

S s;
int* field = &s.field;

writeln(s.isNull); // ok
s.makeNull(); // Error: Variable `s` has a borrow and may not be 
mutated by calling `makeNull`.
```

This also works when not the this pointer, but instead is a 
function parameter by-ref:

```d
void func(ref const S s) {
  	s = S(2); // Error: cannot modify `const` expression `s`
}
```


More information about the dip.ideas mailing list