Skip to content

Commit c239305

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

18 files changed

Lines changed: 3509 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: 274 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,85 @@ 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+
else if (fd.getModule.ident.toString == "a12874")
174+
return;
175+
else if (fd.getModule.ident.toString == "noreturn2")
176+
return;
177+
178+
if (strcmp(mangleExact(fd),
179+
"_D3std4json9JSONValue17__lambda_L726_C31FNaNbNiZSQBvQBuQBs") == 0)
180+
return;
181+
}
103182

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

120199
dfaCommon.allocator.dfaCommon = &dfaCommon;
200+
dfaCommon.currentFunction = fd;
201+
202+
if (auto ag = fd.isThis)
203+
{
204+
if (ag.isClassDeclaration)
205+
dfaCommon.isCurrentFunctionClassConstructor = fd.ident is Id.ctor;
206+
}
121207

122208
stmtWalker.dfaCommon = &dfaCommon;
123209
expWalker.dfaCommon = &dfaCommon;
@@ -133,6 +219,49 @@ void fastDFA(FuncDeclaration fd, Scope* sc)
133219
analyzer.reporter = &reporter;
134220
reporter.errorSink = global.errorSink;
135221

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

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-
161283
version (none)
162284
{
163285
import dmd.hdrgen;
@@ -169,20 +291,151 @@ void fastDFA(FuncDeclaration fd, Scope* sc)
169291
printf(buf.extractChars);
170292
}
171293

172-
stmtWalker.start(fd);
294+
int currentErrors = global.errors;
173295

174-
version (none)
296+
try
175297
{
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);
298+
timeTraceBeginEvent(TimeTraceEventType.dfa);
299+
stmtWalker.start(fd);
300+
}
301+
finally
302+
{
303+
timeTraceEndEvent(TimeTraceEventType.dfa, fd);
179304
}
180305

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);
306+
if (currentErrors != global.errors)
307+
{
308+
version (none)
309+
{
310+
printf("function dfa %s : %s = %s at %s\n", fd.getModule.ident.toChars,
311+
mangleExact(fd), fd.toFullSignature, fd.loc.toChars);
312+
}
184313

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

0 commit comments

Comments
 (0)