Escape Analysis & Owner Escape Analysis

Richard (Rikki) Andrew Cattermole richard at cattermole.co.nz
Wed Sep 4 11:03:46 UTC 2024


On 04/09/2024 10:24 PM, Dennis wrote:
> On Wednesday, 4 September 2024 at 03:02:10 UTC, Richard (Rikki) Andrew 
> Cattermole wrote:
>> It would be an easy enough swap to change ``@safe`` to ``@tsafe``. But 
>> that isn't a decision we need to make here. We can make that prior to 
>> launch.
> 
> `@safe` `@trusted` and `@system` are already misunderstood as they are, 
> we really don't want to throw a fourth attribute into the mix.

Indeed, there are some interesting trade offs here.

But it is an option to give people a way to buy into it (by not being 
forced to use it).

>> But I do want to make a point here, owner escape analysis only kicks 
>> in and forces effectively const on the owner if:
> 
> That's not consistent with this example from the DIP, where there's no 
> `scope` or `&field`:
> 
> ```D
> struct Top {
>      int* field;
> }
> 
> void func(ref Top owner) @safe {
>      int* field = owner.field;
>      // owner is now effectively const, it cannot be mutated
> 
>      owner = Top.init; // Error: The variable `owner` has a borrow and 
> cannot be mutated
> ```

Okay that example is wrong, it was copied from an earlier iteration and 
I didn't think it through.

Will fix.

```d
struct Top {
	int* field;
}

void func(ref Top owner) @safe {
	int** field = &owner.field;
	// owner is now effectively const, it cannot be mutated
	
	owner = Top.init; // Error: The variable `owner` has a borrow and 
cannot be mutated
	owner.field = null; // Error: The variable `owner` has a borrow and 
cannot be mutated

	if (field !is null) {
		writeln(**field);
		**field = 2; // ok, fully mutable
	}
}
```

>> Giving ``scope`` a default escape set is to allow it to match existing 
>> understanding, which does help with communicability.
> 
> That's sending mixed messages. On the one hand, this DIP completely 
> redefines lifetime semantics and syntax, trying to forget DIP1000 ever 
> existed. On the other hand, it adds a special meaning to `scope` 
> feigning some sort of backward compatibility, but adding a new double 
> meaning to the keyword, which is the very thing the new syntax is 
> supposed to fix!

Okay, I can entirely ditch the default escape set for ``scope``. Its not 
required, it only exists as a QoL thing.

Done.

Now ``scope`` by itself won't reflect existing behaviors and require 
additional annotation to make it completely consistent within the proposal.

>> Yes you are correct.
>>
>> If you were allowed to take a pointer to a by-ref variable and then 
>> store it some place you are most likely escaping a pointer.
> 
> The address of the variable and the pointer value it holds are two 
> different things. So the following becomes impossible to express with 
> this DIP:
> 
> ```D
> int* global;
> 
> int** f(return ref int* v) @safe
> {
>      global = v;
>      return &v;
> }
> ```

Yes, that is intentional.

>> I'm going to need an example of what you think is not addressed here.
> 
> To clarify, the headings in my post are common DIP1000 woes that 
> alternative DIPs should have an answer to. Timon has brought up the 
> composability problem before:
> 
> ```D
> import std.typecons;
> int* y;
> int* foo(){
>      int x;
>      auto t=tuple(&x,y); // type has to be Tuple!(scope(int*),int*)
>      return t[1];
> }
> ```
> 
> https://forum.dlang.org/post/qqgjop$kan$1@digitalmars.com
> 
> The example could compile, but it doesn't because the entire tuple 
> shares one lifetime.
> Another example is item 1 of my post: 
> https://forum.dlang.org/post/icoavlbaxqpcnkhijcpy@forum.dlang.org

Yes I'm aware of this one.

It is a complicating factor in the analysis and should be developed 
later on.

We did talk about it on Discord.

If we were to do it right now, we can do POD structs and static arrays. 
But more adjustment would be needed later on for language tuples.

>> From my perspective the field gets conflated with its containing 
>> instance variable and that covers composability.
> 
> So this DIP's answer is: tough luck, we're still conflating.

Yes.

>> ``scope`` is not transitive, at least as far as the language knows 
>> transitive to mean.
> 
> Same here, I meant to say that "lack of transitive scope" is a DIP1000 
> woe that the DIP should address. The DIP doesn't have a single example 
> where a pointer gets dereferenced and then escaped. What happens to the 
> following examples?
> 
> ```D
> // Assuming -preview=dip1000
> 
> int* deref(scope int** x) @safe => *x; // currently allowed
> // because x gets dereferenced and scope only applies to first indirection

Allowed too, but the return value will have a strong relationship.

``int* deref(@escape(return) int** x) @safe => *x;``

Annotating ``scope`` is optional, as it'll be upgraded by caller if needed.

This function is effectively the ``identity`` functions that I use 
throughout the document. So this is covered.

> void main() @safe
> {
>      int x, y;
>      scope int[] arr = [&x, &y]; // currently not allowed
>      // because it requires scope to apply to two levels of pointer 
> indirection

That is safe due to conflation and reverse order of cleanup.

> }
> ```

Okay this would be a good addition.

```d
int* transformation(int* input) {
	int value;

	int*[3] array;
	array[0] = input; // `array` has a weak relationship to `input`
	array[1] = new int; // GC allocation has no relationships without a 
constructor call or initializer to form one
	array[2] = &value;
	return array[0]; // Error: Variable `array` is owned by the stack due 
to the variable `value` and cannot be returned
}
```


More information about the dip.ideas mailing list