Escape analysis (full scope analysis proposal)

Michel Fortin michel.fortin at michelf.com
Wed Oct 29 04:28:55 PDT 2008


On 2008-10-28 23:52:04 -0400, "Robert Jacques" <sandford at jhu.edu> said:

> I've run across some academic work on ownership types which seems 
> relevant  to this discussion on share/local/scope/noscope.

I haven't read the paper yet, but the overview seems to go in the same 
direction as I was thinking.

Basically, all the scope variables you can get are guarentied to be in 
the current or in some ansestry scope. To allow a reference to a scope 
variable, or a scope function, to be put inside a member of a struct or 
class, you only need to prove that the struct or class lifetime is 
smaller or equal to the one of the reference to your scope variable. If 
you could tell to the compiler the scope relationship of the various 
arguments, then you'd have pretty good scope analysis.

For instance, with this syntax, we could define i to be available 
during the whole lifetime of o:

	void foo(scope MyObject o, scope(o) int* i)
	{
		o.i = i;
	}

So you could do:

	void bar()
	{
		scope int i;
		scope MyObject o = new MyObject;
		foo(o, &i);
	}

And the compiler would let it pass because foo guarenties not to keep 
references to i outside of o's scope, and o's scope is the same as i.

Or you could do:

	void test1()
	{
		int i;
		test2(&i);
	}

	void test2(scope int* i)
	{
		scope o = new MyObject;
		foo(o, &i);
	}

Again, the compiler can statically check that test2 won't keep a 
reference to i outside of the caller's scope (test1) because o scope is 
limited to test2.

And if you try the reverse:

	void test1()
	{
		scope o = new MyObject;
		test2(o);
	}

	void test2(scope MyObject o)
	{
		int i;
		foo(o, &i);
	}

Then the compiler could determine automatically that i needs to escape 
test2's scope and allocate the variable on the heap to make its 
lifetime as long as the object's scope (as it does currently with 
nested functions) [see my reserves to this in post scriptum]. This 
could be avoided by explictly binding i to the current scope, in which 
case the compiler could issue a scope error:

	void test2(scope MyObject o)
	{
		scope int i;
		foo(o, &i); // error, i scope needs to match o's, but i is bound to 
the current scope.
	}

Interistingly, with this scheme, assuming your function arguments are 
properly scope-labeled, you never need to allocate variables on the 
heap explicitly anymore, the compiler can take care of it for you when 
the use of the variable inside the function body requires it.

	void test3(int* i); // unscoped parameter
	void test4()
	{
		int i; // allocated on heap because calling test3 requires an 
unscoped variable.
		test3(&i);
	}

The reverse is also true: objects declared as allocated on the heap 
could be automatically rescoped as local stack variables if their use 
inside the function is limited in scope:

	void test5()
	{
		auto o = new MyObject;
		test2(o);
	}

For instance, in test3 above where o isn't declared as scope, the 
compiler could still allocate o on the stack (as long as it knows the 
constructor doesn't leave unwanted references to the object in the 
global state), because it knows from the argument declaration of test2 
that no references to o will leave the current scope.

So basically, what to heap-allocate and what to stack-allocate could be 
left entirely to the compiler's discretion.

Note that for all this to work, the pointer "i" in MyObject must be 
defined as not escaping the scope of the class:

	class MyObject
	{
		scope int* i;
	}

or else someone could take the reference and put it into a global 
variable, or a variable of a greater scope than the object.

P.S.: I'm still somewhat skeptical about this automatic allocation 
thing because it would mean a lot of extra heap allocation (and thus 
loss of performance) for any function where the parameters are not 
properly scoped. Perhaps the default should be local scope and you 
explicitly make it greater by declaring variables as noscope, which 
would allow the compiler to allocate if needed, but it doesn't solve 
the issue of the need to allocate on the heap for calling safely 
functions not using scope-labeled arguments.

P.P.S.: This syntax doesn't fit very well with the current 
scope(success/failure/exit) feature.

-- 
Michel Fortin
michel.fortin at michelf.com
http://michelf.com/




More information about the Digitalmars-d mailing list