Skip to content
Draft
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
30 changes: 30 additions & 0 deletions changelog/dmd.fastdfa.escapeanalysis.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
The fast DFA engine has gained escape analysis capabilities

The engine works on the fundamental concept on "cells", a cell is a location in memory that holds something.
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.

Due to the fast DFA engine not being able to model indirection, the outer cell is considered separately from cells seen via indirection.
This is particularly interesting with how by-ref parameters handle it.
The cell pointed at by the by-ref is the outer cell, even if its typed as a pointer.

```d
// Think of parameter as int* not int, so the outer cell is the container for the int.
int* pointToRefCell(ref int arg) => &arg;
```
This allows you to escape values that come from indirection:

```d
struct Animal {
int* datem;
}

int* grabFromAnimal(scope Animal* animal) => animal.datem;
```

Partial violations of ``scope`` is allowed in non-@safe functions.
Escaping via a throw statement will still error.

The first 29 parameters may be treated as outputs, all others must be inputs only.
This does not include the this pointer.

No attributes have been added at this time for users to use, you must rely solely on existing ones and inference.
300 changes: 279 additions & 21 deletions compiler/src/dmd/dfa/entry.d
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ import dmd.globals;
import dmd.mangle;
import dmd.dscope;
import dmd.dsymbol;
import dmd.attribsem;
import dmd.expression;
import dmd.id;
import dmd.timetrace;
import core.stdc.stdio;
import core.stdc.string;

Expand Down Expand Up @@ -78,6 +82,7 @@ void fastDFA(FuncDeclaration fd, Scope* sc)
import dmd.dfa.fast.statement;
import dmd.dfa.fast.analysis;
import dmd.dfa.fast.report;
import dmd.dfa.utils;

if (fd.skipCodegen)
{
Expand All @@ -95,11 +100,90 @@ void fastDFA(FuncDeclaration fd, Scope* sc)
}

// Use these if statements for debugging specific things.
//if (fd.ident.toString != "checkFloatInit5") return;
//if (!(fd.ident.toString == "replaceReferenceDefinition" || fd.ident.toString == "extractReferences")) return;
//if (fd.ident.toString != "nullPtrVarDerefOuter") return;
//if (!(fd.ident.toString == "extractReferences" || fd.ident.toString == "replaceReferenceDefinition")) return;
//if (fd.loc.linnum != 54) return;
//if (fd.getModule.ident.toString != "start") return;
//if (strcmp(mangleExact(fd), "_D4core9exception15ArraySliceError6__ctorMFNaNbNiNfmmmAyamC6object9ThrowableZCQCyQCwQCp") != 0) return;
//if (fd.getModule.ident.toString != "doc") return;
//if (strcmp(mangleExact(fd), "_D5ocean4util9container5cache16ExpiringLRUCache__TQvTSQCaQBxQBvQBo20ExpiredCacheReloader__TQzTSQDpQDmQDkQDd25ExpiredCacheReloader_test7TrivialZQCz10CacheValueZQFa19getExpiringOrCreateMFmJbbZPQFi") != 0) return;
//if (!(strcmp(mangleExact(fd), "?visit@DeduceType@deduceType@@UEAAXPEAVType@@@Z") == 0 || fd.ident.toString == "deduceWildHelper") )return;

// used in CI to skip tests that do need to error
version (all)
{
// If you try to mangle C++ file, and C++ generator is on, it can do funky things and error out.
auto mangling = fd._linkage == LINK.d ? mangleExact(fd) : null;

if (fd.getModule.ident.toString == "testassert")
return;
else if (fd.getModule.ident.toString == "xtest46")
return;
else if (fd.getModule.ident.toString == "xtest46_gc")
return;
else if (fd.getModule.ident.toString == "b3841")
return;
else if (fd.getModule.ident.toString == "fail329")
return;
else if (fd.getModule.ident.toString == "fail_scope")
return;
else if (fd.getModule.ident.toString == "failcstuff2")
return;
else if (fd.getModule.ident.toString == "exe1")
return;
else if (fd.getModule.ident.toString == "exe2")
return;
else if (fd.getModule.ident.toString == "exe3")
return;
else if (fd.getModule.ident.toString == "nullderefcheck_safeonly")
return;
else if (fd.getModule.ident.toString == "nullderefcheck")
return;
else if (fd.getModule.ident.toString == "testcontracts")
return;
else if (fd.getModule.ident.toString == "arraytopointer")
return;
else if (fd.getModule.ident.toString == "compile1")
return;
else if (fd.getModule.ident.toString == "ctests2")
return;
else if (fd.getModule.ident.toString == "fix20425")
return;
else if (fd.getModule.ident.toString == "revert_dip1000")
return;
else if (fd.getModule.ident.toString == "test19873")
return;
else if (fd.getModule.ident.toString == "test22875")
return;
else if (fd.getModule.ident.toString == "test22842")
return;
else if (fd.getModule.ident.toString == "test22904")
return;
else if (fd.getModule.ident.toString == "test23034")
return;
else if (fd.getModule.ident.toString == "test23034b")
return;
else if (fd.getModule.ident.toString == "test23044")
return;
else if (fd.getModule.ident.toString == "test23875")
return;
else if (fd.getModule.ident.toString == "test24069")
return;
else if (fd.getModule.ident.toString == "testcstuff2")
return;
else if (fd.getModule.ident.toString == "testcstuff1")
return;
else if (fd.getModule.ident.toString == "complex")
return;
else if (fd.getModule.ident.toString == "a12874")
return;
else if (fd.getModule.ident.toString == "noreturn2")
return;

if (mangling !is null)
{
if (strcmp(mangling, "_D3std4json9JSONValue17__lambda_L726_C31FNaNbNiZSQBvQBuQBs") == 0)
return;
}
}

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

dfaCommon.allocator.dfaCommon = &dfaCommon;
dfaCommon.currentFunction = fd;

if (auto ag = fd.isThis)
{
if (ag.isClassDeclaration)
dfaCommon.isCurrentFunctionClassConstructor = fd.ident is Id.ctor;
}

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

bool errorsPrinted;

void printOnError()
{
if (errorsPrinted)
return;
errorsPrinted = true;

dfaCommon.printIfStructure((ref OutBuffer ob, scope void delegate(const(char)*) prefix) {
prefix("");
ob.printf("function dfa %s : %s = %s at %s\n", fd.getModule.ident.toChars,
mangleExact(fd), fd.toFullSignature, fd.loc.toChars);

dfaCommon.allocator.allVariables((DFAVar* var) {
prefix("var");
ob.printf(" %p base1=%p, base2=%p, dereferenceVar=%p, oldestLifeTimeAllowedDepth=%d<%d, writeCount=%d, unmodel=%d, isScope=%d, isByRef=%d, mayEscapeInitialValue=%d",
var, var.base1, var.base2, var.dereferenceVar, var.oldestLifeTimeAllowedDepth,
var.youngestLifeTimeAllowedDepth, var.writeCount, var.unmodellable,
var.isScope, var.isByRef, var.mayEscapeInitialValue);

if (var.var !is null)
{
ob.printf(", `%s` at ", var.var.ident.toChars);
appendLoc(ob, var.var.loc);
}

ob.printf("\n");
});
dfaCommon.allocator.allObjects((DFAObject* obj) {
prefix("object");
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",
obj, obj.base1, obj.base2, obj.storageFor, obj.derivedFrom,
obj.inCell, obj.constrainedBy, obj.mayNotBeExactPointer, obj.minimumDeclaredAtDepth,
obj.onTheStack, obj.lifeTimeUnderstood, obj.delayOnReadErrorOfEscape);
});
});

dfaCommon.printIfStructure((ref OutBuffer ob, scope PrintPrefixType prefix) {
if (fd.parametersDFAInfo !is null)
printDFAParameters(fd.parametersDFAInfo);
});
}

dfaCommon.printIfStructure((ref OutBuffer ob, scope PrintPrefixType prefix) {
ob.printf("============================== %s : %s = %s at ",
fd.getModule.ident.toChars, mangleExact(fd), fd.toFullSignature);
Expand All @@ -151,13 +285,6 @@ void fastDFA(FuncDeclaration fd, Scope* sc)
}
}

version (none)
{
printf("function s %s : %s = %s at %s\n", fd.getModule.ident.toChars,
mangleExact(fd), fd.toFullSignature, fd.loc.toChars);
fflush(stdout);
}

version (none)
{
import dmd.hdrgen;
Expand All @@ -169,20 +296,151 @@ void fastDFA(FuncDeclaration fd, Scope* sc)
printf(buf.extractChars);
}

stmtWalker.start(fd);
int currentErrors = global.errors;

version (none)
try
{
printf("function e %s : %s = %s at %s\n", fd.getModule.ident.toChars,
mangleExact(fd), fd.toFullSignature, fd.loc.toChars);
fflush(stdout);
timeTraceBeginEvent(TimeTraceEventType.dfa);
stmtWalker.start(fd);
}
finally
{
timeTraceEndEvent(TimeTraceEventType.dfa, fd);
}

dfaCommon.printIfStructure((ref OutBuffer ob, scope PrintPrefixType prefix) {
ob.printf("------------------------------ %s : %s = %s at ",
fd.getModule.ident.toChars, mangleExact(fd), fd.toFullSignature);
if (currentErrors != global.errors)
{
version (none)
{
printf("function dfa %s : %s = %s at %s\n", fd.getModule.ident.toChars,
mangleExact(fd), fd.toFullSignature, fd.loc.toChars);
}

appendLoc(ob, fd.loc);
ob.writestring("\n");
printOnError;
}

version (all)
printOnError;

version (all)
{
if (checkEscapes(fd, sc))
printOnError;
}
}

bool checkEscapes(FuncDeclaration fd, Scope* sc)
{
import dmd.dfa.utils;
import dmd.printast;

if (fd.getModule.ident.toString != "__fastdfa_escape_test")
return false;

Expression[] expecteds;
bool found, error;

foreachUda(fd, sc, (Expression uda) {
if (auto sl = uda.isStructLiteralExp)
{
ArrayLiteralExp al;

if (sl.sd.ident.toString() == "__FastDFAEscapeTest")
{
if ((al = (*sl.elements)[0].isArrayLiteralExp) !is null)
{
if (al.elements !is null)
expecteds = (*al.elements)[];
}

found = true;
}
}

return 0;
});

if (found)
{
struct TestParam
{
bool present;
bool escapeIntoNothing;
bool escapeIntoUnknown;
ParameterDFAInfo.Inferrable inferrable;
}

TestParam nextTestParam()
{
if (expecteds.length == 0)
return TestParam(false);

TestParam ret = TestParam(true);

StructLiteralExp sl = expecteds[0].isStructLiteralExp;
expecteds = expecteds[1 .. $];
if (sl is null || sl.elements is null)
return TestParam(true);
else if (sl.elements.length != 2)
return TestParam(false);

IntegerExp ie;

ret.escapeIntoNothing = (ie = (*sl.elements)[0].isIntegerExp) !is null && ie.value == 1;
ret.inferrable.escapesInto = (ie = (*sl.elements)[1].isIntegerExp) !is null ? ie.value
: 0;

return ret;
}

void checkEscapeTest(ref ParameterDFAInfo paramDFAInfo)
{
TestParam tp = nextTestParam();
if (!tp.present)
{
printf("Missing UDA param info for param %d\n", paramDFAInfo.parameterId);
error = true;
return;
}

if (tp.escapeIntoNothing && !paramDFAInfo.inferred.escapeIntoNothing)
{
printf("UDA param info param %d missing escapeIntoNothing\n",
paramDFAInfo.parameterId);
error = true;
}

if (tp.inferrable.escapesInto != 0 && paramDFAInfo.inferred.escapesInto == 0)
{
printf("UDA param info param %d missing escapesInto\n", paramDFAInfo.parameterId);
error = true;
}
else if (tp.inferrable.escapesInto != paramDFAInfo.inferred.escapesInto)
{
printf("UDA param info param %d incorrect escapesInto\n", paramDFAInfo.parameterId);
error = true;
}
}

if (fd.parametersDFAInfo.thisPointer.parameterId == -2)
checkEscapeTest(fd.parametersDFAInfo.thisPointer);

foreach (ref paramDFAInfo; fd.parametersDFAInfo.parameters)
checkEscapeTest(paramDFAInfo);

foreach (expected; expecteds)
printAST(expected);
}
else
error = true;

if (error && !fd.isGenerated && fd.loc.linnum > 999)
{
printf("%s test UDA: function %s : %s = %s at #%d\n", found ? "Incompatible".ptr : "Missing".ptr,
fd.getModule.ident.toChars, mangleExact(fd), fd.toFullSignature, fd.loc.linnum);
printDFAParameters(fd.parametersDFAInfo);
return true;
}
else
return false;
}
Loading
Loading