Skip to content

Commit 59be1b2

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

15 files changed

Lines changed: 3369 additions & 980 deletions

File tree

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: 205 additions & 19 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,23 @@ 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 != "escapeToGlobalSystem2") return;
104+
//if (!(fd.ident.toString == "escapeByRef2" || fd.ident.toString == "escapeByRef3")) return;
100105
//if (fd.loc.linnum != 54) return;
101106
//if (fd.getModule.ident.toString != "start") return;
102-
//if (strcmp(mangleExact(fd), "_D4core9exception15ArraySliceError6__ctorMFNaNbNiNfmmmAyamC6object9ThrowableZCQCyQCwQCp") != 0) 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+
}
103120

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

120137
dfaCommon.allocator.dfaCommon = &dfaCommon;
138+
dfaCommon.currentFunction = fd;
139+
140+
if (auto ag = fd.isThis)
141+
{
142+
if (ag.isClassDeclaration)
143+
dfaCommon.isCurrentFunctionClassConstructor = fd.ident is Id.ctor;
144+
}
121145

122146
stmtWalker.dfaCommon = &dfaCommon;
123147
expWalker.dfaCommon = &dfaCommon;
@@ -133,6 +157,49 @@ void fastDFA(FuncDeclaration fd, Scope* sc)
133157
analyzer.reporter = &reporter;
134158
reporter.errorSink = global.errorSink;
135159

160+
bool errorsPrinted;
161+
162+
void printOnError()
163+
{
164+
if (errorsPrinted)
165+
return;
166+
errorsPrinted = true;
167+
168+
dfaCommon.printIfStructure((ref OutBuffer ob, scope void delegate(const(char)*) prefix) {
169+
prefix("");
170+
ob.printf("function dfa %s : %s = %s at %s\n", fd.getModule.ident.toChars,
171+
mangleExact(fd), fd.toFullSignature, fd.loc.toChars);
172+
173+
dfaCommon.allocator.allVariables((DFAVar* var) {
174+
prefix("var");
175+
ob.printf(" %p base1=%p, base2=%p, dereferenceVar=%p, lifeTimeDepth=%d, writeCount=%d, unmodel=%d, isScope=%d, isByRef=%d, mayEscapeInitialValue=%d",
176+
var, var.base1, var.base2, var.dereferenceVar, var.lifeTimeDepth,
177+
var.writeCount, var.unmodellable, var.isScope, var.isByRef,
178+
var.mayEscapeInitialValue);
179+
180+
if (var.var !is null)
181+
{
182+
ob.printf(", `%s` at ", var.var.ident.toChars);
183+
appendLoc(ob, var.var.loc);
184+
}
185+
186+
ob.printf("\n");
187+
});
188+
dfaCommon.allocator.allObjects((DFAObject* obj) {
189+
prefix("object");
190+
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",
191+
obj, obj.base1, obj.base2, obj.storageFor, obj.derivedFrom,
192+
obj.inCell, obj.constrainedBy, obj.mayNotBeExactPointer, obj.minimumDeclaredAtDepth,
193+
obj.onTheStack, obj.lifeTimeUnderstood, obj.delayOnReadErrorOfEscape);
194+
});
195+
});
196+
197+
dfaCommon.printIfStructure((ref OutBuffer ob, scope PrintPrefixType prefix) {
198+
if (fd.parametersDFAInfo !is null)
199+
printDFAParameters(fd.parametersDFAInfo);
200+
});
201+
}
202+
136203
dfaCommon.printIfStructure((ref OutBuffer ob, scope PrintPrefixType prefix) {
137204
ob.printf("============================== %s : %s = %s at ",
138205
fd.getModule.ident.toChars, mangleExact(fd), fd.toFullSignature);
@@ -151,13 +218,6 @@ void fastDFA(FuncDeclaration fd, Scope* sc)
151218
}
152219
}
153220

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-
161221
version (none)
162222
{
163223
import dmd.hdrgen;
@@ -169,20 +229,146 @@ void fastDFA(FuncDeclaration fd, Scope* sc)
169229
printf(buf.extractChars);
170230
}
171231

172-
stmtWalker.start(fd);
232+
int currentErrors = global.errors;
233+
234+
try
235+
{
236+
timeTraceBeginEvent(TimeTraceEventType.dfa);
237+
stmtWalker.start(fd);
238+
}
239+
finally
240+
{
241+
timeTraceEndEvent(TimeTraceEventType.dfa, fd);
242+
}
243+
244+
version (all)
245+
{
246+
if (currentErrors != global.errors)
247+
printOnError;
248+
}
173249

174250
version (none)
251+
printOnError;
252+
253+
version (all)
175254
{
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);
255+
if (checkEscapes(fd, sc))
256+
printOnError;
179257
}
258+
}
180259

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);
260+
bool checkEscapes(FuncDeclaration fd, Scope* sc)
261+
{
262+
import dmd.dfa.utils;
263+
import dmd.printast;
184264

185-
appendLoc(ob, fd.loc);
186-
ob.writestring("\n");
265+
if (fd.getModule.ident.toString != "__fastdfa_escape_test")
266+
return false;
267+
268+
Expression[] expecteds;
269+
bool found, error;
270+
271+
foreachUda(fd, sc, (Expression uda) {
272+
if (auto sl = uda.isStructLiteralExp)
273+
{
274+
ArrayLiteralExp al;
275+
276+
if (sl.sd.ident.toString() == "__FastDFAEscapeTest")
277+
{
278+
if ((al = (*sl.elements)[0].isArrayLiteralExp) !is null)
279+
{
280+
if (al.elements !is null)
281+
expecteds = (*al.elements)[];
282+
}
283+
284+
found = true;
285+
}
286+
}
287+
288+
return 0;
187289
});
290+
291+
if (found)
292+
{
293+
struct TestParam
294+
{
295+
bool present;
296+
bool escapeIntoNothing;
297+
bool escapeIntoUnknown;
298+
ParameterDFAInfo.Inferrable inferrable;
299+
}
300+
301+
TestParam nextTestParam()
302+
{
303+
if (expecteds.length == 0)
304+
return TestParam(false);
305+
306+
TestParam ret = TestParam(true);
307+
308+
StructLiteralExp sl = expecteds[0].isStructLiteralExp;
309+
expecteds = expecteds[1 .. $];
310+
if (sl is null || sl.elements is null)
311+
return TestParam(true);
312+
else if (sl.elements.length != 2)
313+
return TestParam(false);
314+
315+
IntegerExp ie;
316+
317+
ret.escapeIntoNothing = (ie = (*sl.elements)[0].isIntegerExp) !is null && ie.value == 1;
318+
ret.inferrable.escapesInto = (ie = (*sl.elements)[1].isIntegerExp) !is null ? ie.value
319+
: 0;
320+
321+
return ret;
322+
}
323+
324+
void checkEscapeTest(ref ParameterDFAInfo paramDFAInfo)
325+
{
326+
TestParam tp = nextTestParam();
327+
if (!tp.present)
328+
{
329+
printf("Missing UDA param info for param %d\n", paramDFAInfo.parameterId);
330+
error = true;
331+
return;
332+
}
333+
334+
if (tp.escapeIntoNothing && !paramDFAInfo.inferred.escapeIntoNothing)
335+
{
336+
printf("UDA param info param %d missing escapeIntoNothing\n",
337+
paramDFAInfo.parameterId);
338+
error = true;
339+
}
340+
341+
if (tp.inferrable.escapesInto != 0 && paramDFAInfo.inferred.escapesInto == 0)
342+
{
343+
printf("UDA param info param %d missing escapesInto\n", paramDFAInfo.parameterId);
344+
error = true;
345+
}
346+
else if (tp.inferrable.escapesInto != paramDFAInfo.inferred.escapesInto)
347+
{
348+
printf("UDA param info param %d incorrect escapesInto\n", paramDFAInfo.parameterId);
349+
error = true;
350+
}
351+
}
352+
353+
if (fd.parametersDFAInfo.thisPointer.parameterId == -2)
354+
checkEscapeTest(fd.parametersDFAInfo.thisPointer);
355+
356+
foreach (ref paramDFAInfo; fd.parametersDFAInfo.parameters)
357+
checkEscapeTest(paramDFAInfo);
358+
359+
foreach (expected; expecteds)
360+
printAST(expected);
361+
}
362+
else
363+
error = true;
364+
365+
if (error && !fd.isGenerated && fd.loc.linnum > 999)
366+
{
367+
printf("%s test UDA: function %s : %s = %s at #%d\n", found ? "Incompatible".ptr : "Missing".ptr,
368+
fd.getModule.ident.toChars, mangleExact(fd), fd.toFullSignature, fd.loc.linnum);
369+
printDFAParameters(fd.parametersDFAInfo);
370+
return true;
371+
}
372+
else
373+
return false;
188374
}

0 commit comments

Comments
 (0)