Skip to content

Commit 7ea9cc9

Browse files
committed
Implement fast DFA's escape analysis
1 parent d188a97 commit 7ea9cc9

19 files changed

Lines changed: 3518 additions & 1011 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: 279 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,90 @@ 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 you try to mangle C++ file, and C++ generator is on, it can do funky things and error out.
114+
auto mangling = fd._linkage == LINK.d ? mangleExact(fd) : null;
115+
116+
if (fd.getModule.ident.toString == "testassert")
117+
return;
118+
else if (fd.getModule.ident.toString == "xtest46")
119+
return;
120+
else if (fd.getModule.ident.toString == "xtest46_gc")
121+
return;
122+
else if (fd.getModule.ident.toString == "b3841")
123+
return;
124+
else if (fd.getModule.ident.toString == "fail329")
125+
return;
126+
else if (fd.getModule.ident.toString == "fail_scope")
127+
return;
128+
else if (fd.getModule.ident.toString == "failcstuff2")
129+
return;
130+
else if (fd.getModule.ident.toString == "exe1")
131+
return;
132+
else if (fd.getModule.ident.toString == "exe2")
133+
return;
134+
else if (fd.getModule.ident.toString == "exe3")
135+
return;
136+
else if (fd.getModule.ident.toString == "nullderefcheck_safeonly")
137+
return;
138+
else if (fd.getModule.ident.toString == "nullderefcheck")
139+
return;
140+
else if (fd.getModule.ident.toString == "testcontracts")
141+
return;
142+
else if (fd.getModule.ident.toString == "arraytopointer")
143+
return;
144+
else if (fd.getModule.ident.toString == "compile1")
145+
return;
146+
else if (fd.getModule.ident.toString == "ctests2")
147+
return;
148+
else if (fd.getModule.ident.toString == "fix20425")
149+
return;
150+
else if (fd.getModule.ident.toString == "revert_dip1000")
151+
return;
152+
else if (fd.getModule.ident.toString == "test19873")
153+
return;
154+
else if (fd.getModule.ident.toString == "test22875")
155+
return;
156+
else if (fd.getModule.ident.toString == "test22842")
157+
return;
158+
else if (fd.getModule.ident.toString == "test22904")
159+
return;
160+
else if (fd.getModule.ident.toString == "test23034")
161+
return;
162+
else if (fd.getModule.ident.toString == "test23034b")
163+
return;
164+
else if (fd.getModule.ident.toString == "test23044")
165+
return;
166+
else if (fd.getModule.ident.toString == "test23875")
167+
return;
168+
else if (fd.getModule.ident.toString == "test24069")
169+
return;
170+
else if (fd.getModule.ident.toString == "testcstuff2")
171+
return;
172+
else if (fd.getModule.ident.toString == "testcstuff1")
173+
return;
174+
else if (fd.getModule.ident.toString == "complex")
175+
return;
176+
else if (fd.getModule.ident.toString == "a12874")
177+
return;
178+
else if (fd.getModule.ident.toString == "noreturn2")
179+
return;
180+
181+
if (mangling !is null)
182+
{
183+
if (strcmp(mangling, "_D3std4json9JSONValue17__lambda_L726_C31FNaNbNiZSQBvQBuQBs") == 0)
184+
return;
185+
}
186+
}
103187

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

120204
dfaCommon.allocator.dfaCommon = &dfaCommon;
205+
dfaCommon.currentFunction = fd;
206+
207+
if (auto ag = fd.isThis)
208+
{
209+
if (ag.isClassDeclaration)
210+
dfaCommon.isCurrentFunctionClassConstructor = fd.ident is Id.ctor;
211+
}
121212

122213
stmtWalker.dfaCommon = &dfaCommon;
123214
expWalker.dfaCommon = &dfaCommon;
@@ -133,6 +224,49 @@ void fastDFA(FuncDeclaration fd, Scope* sc)
133224
analyzer.reporter = &reporter;
134225
reporter.errorSink = global.errorSink;
135226

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

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-
161288
version (none)
162289
{
163290
import dmd.hdrgen;
@@ -169,20 +296,151 @@ void fastDFA(FuncDeclaration fd, Scope* sc)
169296
printf(buf.extractChars);
170297
}
171298

172-
stmtWalker.start(fd);
299+
int currentErrors = global.errors;
173300

174-
version (none)
301+
try
175302
{
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);
303+
timeTraceBeginEvent(TimeTraceEventType.dfa);
304+
stmtWalker.start(fd);
305+
}
306+
finally
307+
{
308+
timeTraceEndEvent(TimeTraceEventType.dfa, fd);
179309
}
180310

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);
311+
if (currentErrors != global.errors)
312+
{
313+
version (none)
314+
{
315+
printf("function dfa %s : %s = %s at %s\n", fd.getModule.ident.toChars,
316+
mangleExact(fd), fd.toFullSignature, fd.loc.toChars);
317+
}
184318

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

0 commit comments

Comments
 (0)