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
19 changes: 19 additions & 0 deletions src/dmd/declaration.d
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,8 @@ extern (C++) class VarDeclaration : Declaration
Expression edtor; // if !=null, does the destruction of the variable
IntRange* range; // if !=null, the variable is known to be within the range

VarDeclarations* maybes; // STC.maybescope variables that are assigned to this STC.maybescope variable
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Can this be a D array?
  • The comment should be a Ddoc comment
  • Can we call this maybeScopes, possibleScopeVariables or some variations of those?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. No, as it's variable sized, and we generally avoid D arrays in the front end for that reason.
  2. As the rest or not, such a change should await that
  3. I'm not a big fan of long names

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a big fan of long names

I'm not a fan of names that are not understandable. maybes, "maybe" what?

Copy link
Contributor

@jacob-carlborg jacob-carlborg Jun 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, as it's variable sized, and we generally avoid D arrays in the front end for that reason.

There are several cases where D arrays are used [1] [2] and you're usually arguing for D string instead of C strings, which are D arrays. Also, using a D array would make addMaybe not necessary because D arrays don't need to be newed.

[1] https://github.com/dlang/dmd/blob/master/src/dmd/dclass.d#L56
[2] https://github.com/dlang/dmd/blob/master/src/dmd/dclass.d#L189


final extern (D) this(Loc loc, Type type, Identifier id, Initializer _init, StorageClass storage_class = STC.undefined_)
{
super(id);
Expand Down Expand Up @@ -1632,6 +1634,23 @@ extern (C++) class VarDeclaration : Declaration
{
return sequenceNumber < v.sequenceNumber;
}

/***************************************
* Add variable to maybes[].
* When a maybescope variable `v` is assigned to a maybescope variable `this`,
* we cannot determine if `this` is actually scope until the semantic
* analysis for the function is completed. Thus, we save the data
* until then.
* Params:
* v = an STC.maybescope variable that was assigned to `this`
*/
final void addMaybe(VarDeclaration v)
{
//printf("add %s to %s's list of dependencies\n", v.toChars(), toChars());
if (!maybes)
maybes = new VarDeclarations();
maybes.push(v);
}
}

/***********************************************************
Expand Down
81 changes: 75 additions & 6 deletions src/dmd/escape.d
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ bool checkAssocArrayLiteralEscape(Scope *sc, AssocArrayLiteralExp ae, bool gag)
bool checkParamArgumentEscape(Scope* sc, FuncDeclaration fdc, Identifier par, Expression arg, bool gag)
{
enum log = false;
if (log) printf("checkParamArgumentEscape(arg: %s par: %s)\n", arg.toChars(), par.toChars());
if (log) printf("checkParamArgumentEscape(arg: %s par: %s)\n", arg ? arg.toChars() : "null", par ? par.toChars() : "null");
//printf("type = %s, %d\n", arg.type.toChars(), arg.type.hasPointers());

if (!arg.type.hasPointers())
Expand Down Expand Up @@ -153,14 +153,15 @@ bool checkParamArgumentEscape(Scope* sc, FuncDeclaration fdc, Identifier par, Ex
/* v is not 'scope', and is assigned to a parameter that may escape.
* Therefore, v can never be 'scope'.
*/
if (log) printf("no infer for %s in %s, fdc %s, %d\n",
v.toChars(), sc.func.ident.toChars(), fdc.ident.toChars(), __LINE__);
if (log) printf("no infer for %s in %s loc %s, fdc %s, %d\n",
v.toChars(), sc.func.ident.toChars(), sc.func.loc.toChars(), fdc.ident.toChars(), __LINE__);
v.doNotInferScope = true;
}
}

foreach (VarDeclaration v; er.byref)
{
if (log) printf("byref %s\n", v.toChars());
if (v.isDataseg())
continue;

Expand Down Expand Up @@ -283,6 +284,11 @@ bool checkAssignEscape(Scope* sc, Expression e, bool gag)
bool inferScope = false;
if (va && sc.func && sc.func.type && sc.func.type.ty == Tfunction)
inferScope = (cast(TypeFunction)sc.func.type).trust != TRUST.system;
//printf("inferScope = %d, %d\n", inferScope, (va.storage_class & STCmaybescope) != 0);

// Determine if va is a parameter that is an indirect reference
const bool vaIsRef = va && va.storage_class & STC.parameter &&
(va.storage_class & (STC.ref_ | STC.out_) || va.type.toBasetype().ty == Tclass);

bool result = false;
foreach (VarDeclaration v; er.byvalue)
Expand All @@ -296,7 +302,17 @@ bool checkAssignEscape(Scope* sc, Expression e, bool gag)

Dsymbol p = v.toParent2();

if (!(va && va.isScope()))
if (va && !vaIsRef && !va.isScope() && !v.isScope() &&
(va.storage_class & v.storage_class & (STC.maybescope | STC.variadic)) == STC.maybescope &&
p == sc.func)
{
/* Add v to va's list of dependencies
*/
va.addMaybe(v);
continue;
}

if (!(va && va.isScope()) || vaIsRef)
notMaybeScope(v);

if (v.isScope())
Expand All @@ -314,8 +330,9 @@ bool checkAssignEscape(Scope* sc, Expression e, bool gag)
if (va &&
(va.enclosesLifetimeOf(v) && !(v.storage_class & (STC.parameter | STC.temp)) ||
// va is class reference
ae.e1.op == TOK.dotVariable && va.type.toBasetype().ty == Tclass && (va.enclosesLifetimeOf(v) || !va.isScope) ||
va.storage_class & STC.ref_ && !(v.storage_class & STC.temp)) &&
ae.e1.op == TOK.dotVariable && va.type.toBasetype().ty == Tclass && (va.enclosesLifetimeOf(v) || !va.isScope()) ||
vaIsRef ||
va.storage_class & (STC.ref_ | STC.out_) && !(v.storage_class & STC.temp)) &&
sc.func.setUnsafe())
{
if (!gag)
Expand Down Expand Up @@ -1580,3 +1597,55 @@ else
}
}


/**********************************************
* Have some variables that are maybescopes that were
* assigned values from other maybescope variables.
* Now that semantic analysis of the function is
* complete, we can finalize this by turning off
* maybescope for array elements that cannot be scope.
*
* `va` `v` => `va` `v`
* maybe maybe => scope scope
* scope scope => scope scope
* scope maybe => scope scope
* maybe scope => scope scope
* - - => - -
* - maybe => - -
* - scope => error
* maybe - => scope -
* scope - => scope -
* Params:
* array = array of variables that were assigned to from maybescope variables
*/
void eliminateMaybeScopes(VarDeclaration[] array)
{
enum log = false;
if (log) printf("eliminateMaybeScopes()\n");
bool changes;
do
{
changes = false;
foreach (va; array)
{
if (log) printf(" va = %s\n", va.toChars());
if (!(va.storage_class & (STC.maybescope | STC.scope_)))
{
if (va.maybes)
{
foreach (v; *va.maybes)
{
if (log) printf(" v = %s\n", v.toChars());
if (v.storage_class & STC.maybescope)
{
// v cannot be scope since it is assigned to a non-scope va
notMaybeScope(v);
changes = true;
}
}
}
}
}
} while (changes);
}

31 changes: 31 additions & 0 deletions src/dmd/semantic3.d
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,37 @@ private extern(C++) final class Semantic3Visitor : Visitor

funcdecl.flags &= ~FUNCFLAG.inferScope;

// Eliminate maybescope's
{
// Create and fill array[] with maybe candidates from the `this` and the parameters
VarDeclaration[] array = void;

VarDeclaration[10] tmp = void;
size_t dim = (funcdecl.vthis !is null) + (funcdecl.parameters ? funcdecl.parameters.dim : 0);
if (dim <= tmp.length)
array = tmp[0 .. dim];
else
{
auto ptr = cast(VarDeclaration*)mem.xmalloc(dim * VarDeclaration.sizeof);
array = ptr[0 .. dim];
}
size_t n = 0;
if (funcdecl.vthis)
array[n++] = funcdecl.vthis;
if (funcdecl.parameters)
{
foreach (v; *funcdecl.parameters)
{
array[n++] = v;
}
}

eliminateMaybeScopes(array[0 .. n]);

if (dim > tmp.length)
mem.xfree(array.ptr);
}

// Infer STC.scope_
if (funcdecl.parameters && !funcdecl.errors)
{
Expand Down
117 changes: 117 additions & 0 deletions test/fail_compilation/retscope6.d
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,120 @@ int* test() @safe
arr[0] ~= &i;
return arr[0][0];
}

/* TEST_OUTPUT:
---
fail_compilation/retscope6.d(7034): Error: reference to local variable `i` assigned to non-scope parameter `_param_1` calling retscope6.S.emplace!(int*).emplace
fail_compilation/retscope6.d(7035): Error: reference to local variable `i` assigned to non-scope parameter `_param_0` calling retscope6.S.emplace2!(int*).emplace2
fail_compilation/retscope6.d(7024): Error: scope variable `_param_2` assigned to `s` with longer lifetime
fail_compilation/retscope6.d(7025): Error: scope variable `_param_2` assigned to `t` with longer lifetime
fail_compilation/retscope6.d(7037): Error: template instance `retscope6.S.emplace4!(int*)` error instantiating
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend a single section of test output, it will be much easier if the error messages need to be updated. It caused me quite some pain in another PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do multiple blocks with a #line precisely because they are easier to update. Adding a line doesn't require updating every message line.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

./run.d fail_compilation AUTO_UPDATE=1 for the win...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this only works with a single output section.

---
*/

#line 7000

alias T = int*;

struct S
{
T payload;

static void emplace(Args...)(ref S s, Args args) @safe
{
s.payload = args[0];
}

void emplace2(Args...)(Args args) @safe
{
payload = args[0];
}

static void emplace3(Args...)(S s, Args args) @safe
{
s.payload = args[0];
}

static void emplace4(Args...)(scope ref S s, scope out S t, scope Args args) @safe
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://dlang.org/spec/attribute.html#scope

scope cannot be applied to globals, statics, data members, ref or out parameters.

This is violating the spec. The spec needs an update.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://dlang.org/spec/attribute.html#scope

Assignment to a scope, other than initialization, is not allowed.

Also not accurate.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I've mentioned before, dip1000 has not been folded into the spec.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and that's part of the problem. Why hasn't it been done? And shouldn't it be done either prior to PRs like this or together with PRs like this?

Copy link
Member Author

@WalterBright WalterBright Jun 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why hasn't it been done?

One large reason is because I can't get my spec fixes pulled.

Aside from that, DIP1000 despite being years old has little use, so I don't know how well it works. I can't get people to use DIP1000 features because phobos doesn't compile with dip1000. So I try to get phobos to compile with dip1000. I run into problems. I make corrections (like this PR). And I get blocked.

End result, no progress anywhere.

There is another issue. I used to work at Boeing in design. The designs ended up as drawings, using pen and ink on special plastic paper. Unsurprisingly, revising these drawings (including redrawing them) is quite expensive. But the designs changed constantly (bug fixes, improvements, changing requirements, the same issues that affect software). What was done was issuing DCN (Drawing Change Notices), which contained the fixes to the drawing. The official "drawing" was the original drawing plus a stack of DCNs, applied in chronological order (much like git, git is hardly a new concept). Eventually, the drawing may get redone with all the DCNs folded in.

The analogy for us is the spec, plus approved DIPs, plus Bugzilla Enhancement Requests and bug fixes. It's not ideal, but it's pragmatic. Halting all progress because it all hasn't been folded into the spec yet is not pragmatic, especially when my PRs to update the spec just sit there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One large reason is because I can't get my spec fixes pulled.

I suggest putting a time limit on those PRs that haven't received feedback. We already have a `72h no objection -> merge" label in the DMD repository, and I don't see why that can't be applied to your dlang.org PRs. Noone has a right to complain if they chose not to participate.

There is another issue. I used to work at Boeing in design...

I understand that, but I'm not going to be your rubber stamp. I need to understand what these PRs are doing and why.

Others on this team are well within their charter to move this PR forward if they understand it and have confidence in the direction you're heading, but I'm not on board yet and have concerns. I've already sent you an e-mail or two about the way scope is being inferred, and I don't like what I'm seeing in the implementation so far. I think we need a way to set storage classes meta-programmatically, rather than reaching for inference.

{
s.payload = args[0];
t.payload = args[0];
Copy link
Contributor

@JinShil JinShil Jun 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the lack of documentation surrounding the semantics of scope I'm assuming that in this context, scope means that args[0]'s lifetime must not exceed the lifetime of this emplace4's scope. However, since we are assigning to a ref and out we are escaping out of emplace4's scope. Is this correct?

When I analyze the implementation of foo, however, there's no escape because everything is local to foo. Perhaps this could use a more compelling test that would actually result in a leak or memory corruption without this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add some more documentation.

Copy link
Contributor

@JinShil JinShil Jun 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WalterBright Is it really too much to ask to have you open a dlang.org PR to document the semantics of scope and return function parameters with illustrative examples so we can refer to it when we review these PRs? Or are you still trying to figure all this out, and this is your way of working through it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's documented in DIP25 and DIP1000. I'd start with a gentler introduction, http://dconf.org/2017/talks/bright.html

}

}

void foo() @safe
{
S s;
int i;
s.emplace(s, &i);
s.emplace2(&i);
s.emplace3(s, &i);
s.emplace4(s, s, &i);
}


/* TEST_OUTPUT:
---
fail_compilation/retscope6.d(8016): Error: reference to local variable `i` assigned to non-scope parameter `s` calling retscope6.frank!().frank
fail_compilation/retscope6.d(8031): Error: reference to local variable `i` assigned to non-scope parameter `p` calling retscope6.betty!().betty
fail_compilation/retscope6.d(8031): Error: reference to local variable `j` assigned to non-scope parameter `q` calling retscope6.betty!().betty
fail_compilation/retscope6.d(8048): Error: reference to local variable `j` assigned to non-scope parameter `q` calling retscope6.archie!().archie
---
*/

// https://issues.dlang.org/show_bug.cgi?id=19035

#line 8000
@safe
{

void escape(int*);

/**********************/

void frank()(ref scope int* p, int* s)
{
p = s; // should error here
}

void testfrankly()
{
int* p;
int i;
frank(p, &i);
}

/**********************/

void betty()(int* p, int* q)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's interesting that you chose to use a template here? Why is that? Is it to ensure the body of the function is needed in order to do the inference. For example, I noticed that Example 1 does not compile, but Example 2 does.

Example 1

@safe:
void betty(int* p, int* q)  // Notice this is not a template
{
     p = q;
}

void testbetty()
{
    int i;
    int j;
    betty(&i, &j); // Error: reference to local variable i assigned to non-scope parameter p calling betty
                   // Error: reference to local variable j assigned to non-scope parameter q calling betty
}

Example 2

@safe:
void betty()(int* p, int* q)  // Notice this is a template
{
     p = q;
}

void testbetty()
{
    int i;
    int j;
    betty(&i, &j);
}

There's a lot you're not saying here, and I think you're taking a lot for granted expecting us to review the semantics of this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Templates have the scope-ness of their parameters (and other attributes) inferred. Regular functions do not. Yes, I did take such knowledge for granted. Now you know!

{
p = q;
escape(p);
}

void testbetty()
{
int i;
int j;
betty(&i, &j); // should error on i and j
}

/**********************/

void archie()(int* p, int* q, int* r)
{
p = q;
r = p;
escape(q);
}

void testarchie()
{
int i;
int j;
int k;
archie(&i, &j, &k); // should error on j
}

}