Skip to content

Commit b79a296

Browse files
committed
Implement fast DFA's escape analysis
1 parent 23ae810 commit b79a296

18 files changed

Lines changed: 3505 additions & 1008 deletions
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
The fast DFA engine has gained escape analysis capabilities
2+
3+
The engine works on the fundamental concept on "cells", a cell is a location in memory that holds something.
4+
So a variable that is on the stack has a cell that provides it, but also may point to another cell if its typed as a pointer.
5+
6+
Due to the fast DFA engine not being able to model indirection, the outer cell is considered separately from cells seen via indirection.
7+
This is particularly interesting with how by-ref parameters handle it.
8+
The cell pointed at by the by-ref is the outer cell, even if its typed as a pointer.
9+
10+
```d
11+
// Think of parameter as int* not int, so the outer cell is the container for the int.
12+
int* pointToRefCell(ref int arg) => &arg;
13+
```
14+
This allows you to escape values that come from indirection:
15+
16+
```d
17+
struct Animal {
18+
int* datem;
19+
}
20+
21+
int* grabFromAnimal(scope Animal* animal) => animal.datem;
22+
```
23+
24+
Partial violations of ``scope`` is allowed in non-@safe functions.
25+
Escaping via a throw statement will still error.
26+
27+
The first 29 parameters may be treated as outputs, all others must be inputs only.
28+
This does not include the this pointer.
29+
30+
No attributes have been added at this time for users to use, you must rely solely on existing ones and inference.

compiler/src/dmd/dfa/entry.d

Lines changed: 270 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ import dmd.globals;
2929
import dmd.mangle;
3030
import dmd.dscope;
3131
import dmd.dsymbol;
32+
import dmd.attribsem;
33+
import dmd.expression;
34+
import dmd.id;
35+
import dmd.timetrace;
3236
import core.stdc.stdio;
3337
import core.stdc.string;
3438

@@ -78,6 +82,7 @@ void fastDFA(FuncDeclaration fd, Scope* sc)
7882
import dmd.dfa.fast.statement;
7983
import dmd.dfa.fast.analysis;
8084
import dmd.dfa.fast.report;
85+
import dmd.dfa.utils;
8186

8287
if (fd.skipCodegen)
8388
{
@@ -95,11 +100,81 @@ void fastDFA(FuncDeclaration fd, Scope* sc)
95100
}
96101

97102
// Use these if statements for debugging specific things.
98-
//if (fd.ident.toString != "checkFloatInit5") return;
99-
//if (!(fd.ident.toString == "replaceReferenceDefinition" || fd.ident.toString == "extractReferences")) return;
103+
//if (fd.ident.toString != "nullPtrVarDerefOuter") return;
104+
//if (!(fd.ident.toString == "extractReferences" || fd.ident.toString == "replaceReferenceDefinition")) return;
100105
//if (fd.loc.linnum != 54) return;
101-
//if (fd.getModule.ident.toString != "start") return;
102-
//if (strcmp(mangleExact(fd), "_D4core9exception15ArraySliceError6__ctorMFNaNbNiNfmmmAyamC6object9ThrowableZCQCyQCwQCp") != 0) return;
106+
//if (fd.getModule.ident.toString != "doc") return;
107+
//if (strcmp(mangleExact(fd), "_D5ocean4util9container5cache16ExpiringLRUCache__TQvTSQCaQBxQBvQBo20ExpiredCacheReloader__TQzTSQDpQDmQDkQDd25ExpiredCacheReloader_test7TrivialZQCz10CacheValueZQFa19getExpiringOrCreateMFmJbbZPQFi") != 0) return;
108+
//if (!(strcmp(mangleExact(fd), "?visit@DeduceType@deduceType@@UEAAXPEAVType@@@Z") == 0 || fd.ident.toString == "deduceWildHelper") )return;
109+
110+
// used in CI to skip tests that do need to error
111+
version (all)
112+
{
113+
if (fd.getModule.ident.toString == "testassert")
114+
return;
115+
else if (fd.getModule.ident.toString == "xtest46")
116+
return;
117+
else if (fd.getModule.ident.toString == "xtest46_gc")
118+
return;
119+
else if (fd.getModule.ident.toString == "b3841")
120+
return;
121+
else if (fd.getModule.ident.toString == "fail329")
122+
return;
123+
else if (fd.getModule.ident.toString == "fail_scope")
124+
return;
125+
else if (fd.getModule.ident.toString == "failcstuff2")
126+
return;
127+
else if (fd.getModule.ident.toString == "exe1")
128+
return;
129+
else if (fd.getModule.ident.toString == "exe2")
130+
return;
131+
else if (fd.getModule.ident.toString == "exe3")
132+
return;
133+
else if (fd.getModule.ident.toString == "nullderefcheck_safeonly")
134+
return;
135+
else if (fd.getModule.ident.toString == "nullderefcheck")
136+
return;
137+
else if (fd.getModule.ident.toString == "testcontracts")
138+
return;
139+
else if (fd.getModule.ident.toString == "arraytopointer")
140+
return;
141+
else if (fd.getModule.ident.toString == "compile1")
142+
return;
143+
else if (fd.getModule.ident.toString == "ctests2")
144+
return;
145+
else if (fd.getModule.ident.toString == "fix20425")
146+
return;
147+
else if (fd.getModule.ident.toString == "revert_dip1000")
148+
return;
149+
else if (fd.getModule.ident.toString == "test19873")
150+
return;
151+
else if (fd.getModule.ident.toString == "test22875")
152+
return;
153+
else if (fd.getModule.ident.toString == "test22842")
154+
return;
155+
else if (fd.getModule.ident.toString == "test22904")
156+
return;
157+
else if (fd.getModule.ident.toString == "test23034")
158+
return;
159+
else if (fd.getModule.ident.toString == "test23034b")
160+
return;
161+
else if (fd.getModule.ident.toString == "test23044")
162+
return;
163+
else if (fd.getModule.ident.toString == "test23875")
164+
return;
165+
else if (fd.getModule.ident.toString == "test24069")
166+
return;
167+
else if (fd.getModule.ident.toString == "testcstuff2")
168+
return;
169+
else if (fd.getModule.ident.toString == "testcstuff1")
170+
return;
171+
else if (fd.getModule.ident.toString == "complex")
172+
return;
173+
174+
if (strcmp(mangleExact(fd),
175+
"_D3std4json9JSONValue17__lambda_L726_C31FNaNbNiZSQBvQBuQBs") == 0)
176+
return;
177+
}
103178

104179
// Protect functions based upon safetiness of it.
105180
// It may be desirable to disable some behaviors in @system code, or completely.
@@ -118,6 +193,13 @@ void fastDFA(FuncDeclaration fd, Scope* sc)
118193
DFAReporter reporter;
119194

120195
dfaCommon.allocator.dfaCommon = &dfaCommon;
196+
dfaCommon.currentFunction = fd;
197+
198+
if (auto ag = fd.isThis)
199+
{
200+
if (ag.isClassDeclaration)
201+
dfaCommon.isCurrentFunctionClassConstructor = fd.ident is Id.ctor;
202+
}
121203

122204
stmtWalker.dfaCommon = &dfaCommon;
123205
expWalker.dfaCommon = &dfaCommon;
@@ -133,6 +215,49 @@ void fastDFA(FuncDeclaration fd, Scope* sc)
133215
analyzer.reporter = &reporter;
134216
reporter.errorSink = global.errorSink;
135217

218+
bool errorsPrinted;
219+
220+
void printOnError()
221+
{
222+
if (errorsPrinted)
223+
return;
224+
errorsPrinted = true;
225+
226+
dfaCommon.printIfStructure((ref OutBuffer ob, scope void delegate(const(char)*) prefix) {
227+
prefix("");
228+
ob.printf("function dfa %s : %s = %s at %s\n", fd.getModule.ident.toChars,
229+
mangleExact(fd), fd.toFullSignature, fd.loc.toChars);
230+
231+
dfaCommon.allocator.allVariables((DFAVar* var) {
232+
prefix("var");
233+
ob.printf(" %p base1=%p, base2=%p, dereferenceVar=%p, oldestLifeTimeAllowedDepth=%d<%d, writeCount=%d, unmodel=%d, isScope=%d, isByRef=%d, mayEscapeInitialValue=%d",
234+
var, var.base1, var.base2, var.dereferenceVar, var.oldestLifeTimeAllowedDepth,
235+
var.youngestLifeTimeAllowedDepth, var.writeCount, var.unmodellable,
236+
var.isScope, var.isByRef, var.mayEscapeInitialValue);
237+
238+
if (var.var !is null)
239+
{
240+
ob.printf(", `%s` at ", var.var.ident.toChars);
241+
appendLoc(ob, var.var.loc);
242+
}
243+
244+
ob.printf("\n");
245+
});
246+
dfaCommon.allocator.allObjects((DFAObject* obj) {
247+
prefix("object");
248+
ob.printf(" %p base1=%p, base2=%p, storageFor=%p, derivedFrom=%p, inCell=%p, constrainedBy=%p, mayNotBeExactPointer=%d, minimumDeclaredAtDepth=%d, onTheStack=%d, lifeTimeUnderstood=%d, delayOnReadErrorOfEscape=%d\n",
249+
obj, obj.base1, obj.base2, obj.storageFor, obj.derivedFrom,
250+
obj.inCell, obj.constrainedBy, obj.mayNotBeExactPointer, obj.minimumDeclaredAtDepth,
251+
obj.onTheStack, obj.lifeTimeUnderstood, obj.delayOnReadErrorOfEscape);
252+
});
253+
});
254+
255+
dfaCommon.printIfStructure((ref OutBuffer ob, scope PrintPrefixType prefix) {
256+
if (fd.parametersDFAInfo !is null)
257+
printDFAParameters(fd.parametersDFAInfo);
258+
});
259+
}
260+
136261
dfaCommon.printIfStructure((ref OutBuffer ob, scope PrintPrefixType prefix) {
137262
ob.printf("============================== %s : %s = %s at ",
138263
fd.getModule.ident.toChars, mangleExact(fd), fd.toFullSignature);
@@ -151,13 +276,6 @@ void fastDFA(FuncDeclaration fd, Scope* sc)
151276
}
152277
}
153278

154-
version (none)
155-
{
156-
printf("function s %s : %s = %s at %s\n", fd.getModule.ident.toChars,
157-
mangleExact(fd), fd.toFullSignature, fd.loc.toChars);
158-
fflush(stdout);
159-
}
160-
161279
version (none)
162280
{
163281
import dmd.hdrgen;
@@ -169,20 +287,151 @@ void fastDFA(FuncDeclaration fd, Scope* sc)
169287
printf(buf.extractChars);
170288
}
171289

172-
stmtWalker.start(fd);
290+
int currentErrors = global.errors;
173291

174-
version (none)
292+
try
175293
{
176-
printf("function e %s : %s = %s at %s\n", fd.getModule.ident.toChars,
177-
mangleExact(fd), fd.toFullSignature, fd.loc.toChars);
178-
fflush(stdout);
294+
timeTraceBeginEvent(TimeTraceEventType.dfa);
295+
stmtWalker.start(fd);
296+
}
297+
finally
298+
{
299+
timeTraceEndEvent(TimeTraceEventType.dfa, fd);
179300
}
180301

181-
dfaCommon.printIfStructure((ref OutBuffer ob, scope PrintPrefixType prefix) {
182-
ob.printf("------------------------------ %s : %s = %s at ",
183-
fd.getModule.ident.toChars, mangleExact(fd), fd.toFullSignature);
302+
if (currentErrors != global.errors)
303+
{
304+
version (none)
305+
{
306+
printf("function dfa %s : %s = %s at %s\n", fd.getModule.ident.toChars,
307+
mangleExact(fd), fd.toFullSignature, fd.loc.toChars);
308+
}
184309

185-
appendLoc(ob, fd.loc);
186-
ob.writestring("\n");
310+
printOnError;
311+
}
312+
313+
version (all)
314+
printOnError;
315+
316+
version (all)
317+
{
318+
if (checkEscapes(fd, sc))
319+
printOnError;
320+
}
321+
}
322+
323+
bool checkEscapes(FuncDeclaration fd, Scope* sc)
324+
{
325+
import dmd.dfa.utils;
326+
import dmd.printast;
327+
328+
if (fd.getModule.ident.toString != "__fastdfa_escape_test")
329+
return false;
330+
331+
Expression[] expecteds;
332+
bool found, error;
333+
334+
foreachUda(fd, sc, (Expression uda) {
335+
if (auto sl = uda.isStructLiteralExp)
336+
{
337+
ArrayLiteralExp al;
338+
339+
if (sl.sd.ident.toString() == "__FastDFAEscapeTest")
340+
{
341+
if ((al = (*sl.elements)[0].isArrayLiteralExp) !is null)
342+
{
343+
if (al.elements !is null)
344+
expecteds = (*al.elements)[];
345+
}
346+
347+
found = true;
348+
}
349+
}
350+
351+
return 0;
187352
});
353+
354+
if (found)
355+
{
356+
struct TestParam
357+
{
358+
bool present;
359+
bool escapeIntoNothing;
360+
bool escapeIntoUnknown;
361+
ParameterDFAInfo.Inferrable inferrable;
362+
}
363+
364+
TestParam nextTestParam()
365+
{
366+
if (expecteds.length == 0)
367+
return TestParam(false);
368+
369+
TestParam ret = TestParam(true);
370+
371+
StructLiteralExp sl = expecteds[0].isStructLiteralExp;
372+
expecteds = expecteds[1 .. $];
373+
if (sl is null || sl.elements is null)
374+
return TestParam(true);
375+
else if (sl.elements.length != 2)
376+
return TestParam(false);
377+
378+
IntegerExp ie;
379+
380+
ret.escapeIntoNothing = (ie = (*sl.elements)[0].isIntegerExp) !is null && ie.value == 1;
381+
ret.inferrable.escapesInto = (ie = (*sl.elements)[1].isIntegerExp) !is null ? ie.value
382+
: 0;
383+
384+
return ret;
385+
}
386+
387+
void checkEscapeTest(ref ParameterDFAInfo paramDFAInfo)
388+
{
389+
TestParam tp = nextTestParam();
390+
if (!tp.present)
391+
{
392+
printf("Missing UDA param info for param %d\n", paramDFAInfo.parameterId);
393+
error = true;
394+
return;
395+
}
396+
397+
if (tp.escapeIntoNothing && !paramDFAInfo.inferred.escapeIntoNothing)
398+
{
399+
printf("UDA param info param %d missing escapeIntoNothing\n",
400+
paramDFAInfo.parameterId);
401+
error = true;
402+
}
403+
404+
if (tp.inferrable.escapesInto != 0 && paramDFAInfo.inferred.escapesInto == 0)
405+
{
406+
printf("UDA param info param %d missing escapesInto\n", paramDFAInfo.parameterId);
407+
error = true;
408+
}
409+
else if (tp.inferrable.escapesInto != paramDFAInfo.inferred.escapesInto)
410+
{
411+
printf("UDA param info param %d incorrect escapesInto\n", paramDFAInfo.parameterId);
412+
error = true;
413+
}
414+
}
415+
416+
if (fd.parametersDFAInfo.thisPointer.parameterId == -2)
417+
checkEscapeTest(fd.parametersDFAInfo.thisPointer);
418+
419+
foreach (ref paramDFAInfo; fd.parametersDFAInfo.parameters)
420+
checkEscapeTest(paramDFAInfo);
421+
422+
foreach (expected; expecteds)
423+
printAST(expected);
424+
}
425+
else
426+
error = true;
427+
428+
if (error && !fd.isGenerated && fd.loc.linnum > 999)
429+
{
430+
printf("%s test UDA: function %s : %s = %s at #%d\n", found ? "Incompatible".ptr : "Missing".ptr,
431+
fd.getModule.ident.toChars, mangleExact(fd), fd.toFullSignature, fd.loc.linnum);
432+
printDFAParameters(fd.parametersDFAInfo);
433+
return true;
434+
}
435+
else
436+
return false;
188437
}

0 commit comments

Comments
 (0)