Skip to content

Commit

Permalink
Properly fix Issue 15296 - expand CallExp in ExpStatement as statements
Browse files Browse the repository at this point in the history
If a `CallExp`, `CondExp`, or `CommaExp` appears in `ExpStatement`, it can be inlined as statements.
It provides more inlining opportunity. Even if the called function contains some statements which cannot be expanded as expressions (e.g. `ForStatement`), the function call can be inlined.

In `expandInline`, if `vthis` is a temporary variable, its dtor call should be deferred till the end of expanded function body statements.
  • Loading branch information
9rnsr committed Feb 13, 2016
1 parent 9c61ff9 commit 25453b6
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 40 deletions.
112 changes: 79 additions & 33 deletions src/inline.d
Expand Up @@ -47,11 +47,11 @@ enum EXPANDINLINE_LOG = false;
*/
enum COST_MAX = 250;
enum STATEMENT_COST = 0x1000;
enum STATEMENT_COST_MAX = 250 * 0x1000;
enum STATEMENT_COST_MAX = 250 * STATEMENT_COST;

// STATEMENT_COST be power of 2 and greater than COST_MAX
//static assert((STATEMENT_COST & (STATEMENT_COST - 1)) == 0);
//static assert(STATEMENT_COST > COST_MAX);
static assert((STATEMENT_COST & (STATEMENT_COST - 1)) == 0);
static assert(STATEMENT_COST > COST_MAX);

bool tooCostly(int cost)
{
Expand Down Expand Up @@ -1201,36 +1201,59 @@ public:
{
printf("ExpStatement.inlineScan(%s)\n", s.toChars());
}
if (s.exp)
if (!s.exp)
return;

Statement inlineScanExpAsStatement(ref Expression exp)
{
/* TODO: It's a problematic inlineScan call. If s.exp is a TOKcall,
* CallExp.inlineScan would try to expand the call as expression.
* If it's impossible, a false "cannot inline function" error
* would be reported.
/* If there's a TOKcall at the top, then it may fail to inline
* as an Expression. Try to inline as a Statement instead.
*/
inlineScan(s.exp); // inline as an expression
if (exp.op == TOKcall)
{
visitCallExp(cast(CallExp)exp, null, true);
if (eresult)
exp = eresult;
auto s = sresult;
sresult = null;
eresult = null;
return s;
}

/* If there's a TOKcall at the top, then it failed to inline
* as an Expression. Try to inline as a Statement instead.
* Note that inline scanning of s.exp.e1 and s.exp.arguments was already done.
/* If there's a CondExp or CommaExp at the top, then its
* sub-expressions may be inlined as statements.
*/
if (s.exp && s.exp.op == TOKcall)
if (exp.op == TOKquestion)
{
CallExp ce = cast(CallExp)s.exp;

/* Workaround for Bugzilla 15296.
*
* Before the PR#5121, here was inlined a function call only
* when ce.e1.op == TOKvar.
* After the PR, visitCallExp has started to handle TOKdotvar
* and TOKstar. However it was not good for the issue case.
*
* Revive a restriction which was in previous code to avoid regression.
*/
if (ce.e1.op == TOKvar)
visitCallExp(ce, null, true);
auto e = cast(CondExp)exp;
inlineScan(e.econd);
auto s1 = inlineScanExpAsStatement(e.e1);
auto s2 = inlineScanExpAsStatement(e.e2);
if (!s1 && !s2)
return null;
auto ifbody = !s1 ? new ExpStatement(e.e1.loc, e.e1) : s1;
auto elsebody = !s2 ? new ExpStatement(e.e2.loc, e.e2) : s2;
return new IfStatement(exp.loc, null, e.econd, ifbody, elsebody);
}
if (exp.op == TOKcomma)
{
auto e = cast(CommaExp)exp;
auto s1 = inlineScanExpAsStatement(e.e1);
auto s2 = inlineScanExpAsStatement(e.e2);
if (!s1 && !s2)
return null;
auto a = new Statements();
a.push(!s1 ? new ExpStatement(e.e1.loc, e.e1) : s1);
a.push(!s2 ? new ExpStatement(e.e2.loc, e.e2) : s2);
return new CompoundStatement(exp.loc, a);
}

// inline as an expression
inlineScan(exp);
return null;
}

sresult = inlineScanExpAsStatement(s.exp);
}

override void visit(CompoundStatement s)
Expand Down Expand Up @@ -1484,8 +1507,7 @@ public:
*/
inlineScan(e.e1);
}
inlineScan(ce.e1);
arrayInlineScan(ce.arguments);

visitCallExp(ce, e.e1, false);
if (eresult)
{
Expand All @@ -1501,8 +1523,6 @@ public:
override void visit(CallExp e)
{
//printf("CallExp.inlineScan() %s\n", e.toChars());
inlineScan(e.e1);
arrayInlineScan(e.arguments);
visitCallExp(e, null, false);
}

Expand All @@ -1513,11 +1533,16 @@ public:
* e = the function call
* eret = if !null, then this is the lvalue of the nrvo function result
* asStatements = if inline as statements rather than as an Expression
* Returns:
* this.eresult if asStatements == false
* this.sresult if asStatements == true
*/
void visitCallExp(CallExp e, Expression eret, bool asStatements)
{
//printf("visitCallExp() %s\n", e.toChars());
inlineScan(e.e1);
arrayInlineScan(e.arguments);

//printf("visitCallExp() %s\n", e.toChars());
FuncDeclaration fd;

void inlineFd()
Expand Down Expand Up @@ -2200,20 +2225,41 @@ void expandInline(Loc callLoc, FuncDeclaration fd, FuncDeclaration parent, Expre
{
/* Construct:
* { eret; ethis; eparams; fd.fbody; }
* or:
* { eret; ethis; try { eparams; fd.fbody; } finally { vthis.edtor; } }
*/

auto as = new Statements();
if (eret)
as.push(new ExpStatement(callLoc, eret));
if (ethis)
as.push(new ExpStatement(callLoc, ethis));

auto as2 = as;
if (vthis && !vthis.isDataseg())
{
if (vthis.needsScopeDtor())
{
// same with ExpStatement.scopeCode()
as2 = new Statements();
vthis.noscope = 1;
}
}

if (eparams)
as.push(new ExpStatement(callLoc, eparams));
as2.push(new ExpStatement(callLoc, eparams));

fd.inlineNest++;
Statement s = inlineAsStatement(fd.fbody, ids);
fd.inlineNest--;
as.push(s);
as2.push(s);

if (as2 != as)
{
as.push(new TryFinallyStatement(callLoc,
new CompoundStatement(callLoc, as2),
new DtorExpStatement(callLoc, vthis.edtor, vthis)));
}

sresult = new ScopeStatement(callLoc, new CompoundStatement(callLoc, as));

Expand Down
14 changes: 7 additions & 7 deletions test/fail_compilation/pragmainline2.d
Expand Up @@ -16,15 +16,15 @@ void foo()
pragma(inline, false);
pragma(inline);
pragma(inline, true); // this last one will affect to the 'foo'
while (0) { }
asm { nop; }
}

pragma(inline, true) void f1t() { while (0) {} } // cannot inline
pragma(inline, false) void f1f() { while (0) {} }
pragma(inline) void f1d() { while (0) {} }
void f2t() { pragma(inline, true); while (0) {} } // cannot inline
void f2f() { pragma(inline, false); while (0) {} }
void f2d() { pragma(inline); while (0) {} }
pragma(inline, true) void f1t() { asm { nop; } } // cannot inline
pragma(inline, false) void f1f() { asm { nop; } }
pragma(inline) void f1d() { asm { nop; } }
void f2t() { pragma(inline, true); asm { nop; } } // cannot inline
void f2f() { pragma(inline, false); asm { nop; } }
void f2d() { pragma(inline); asm { nop; } }

void main()
{
Expand Down
91 changes: 91 additions & 0 deletions test/runnable/inline.d
Expand Up @@ -953,6 +953,7 @@ static int x15296;
struct S15296
{
// Can be expanded only as statements.
pragma(inline, true)
void bar(size_t , size_t )
{
for (size_t w = 0; w < 2; w++) { ++x15296; }
Expand All @@ -965,14 +966,102 @@ struct S15296
}
}

pragma(inline, true)
static void voidCall15296()
{
for (size_t w = 0; w < 3; w++) { ++x15296; }
}

void test15296()
{
bool cond = true;

S15296 s;

// CallExp at the top of ExpStatement
x15296 = 0;
s.foo(0, 0);
assert(x15296 == 2);

// CondExp at the top of ExpStatement
x15296 = 0;
(cond ? s.foo(0, 0) : voidCall15296());
assert(x15296 == 2);
(cond ? voidCall15296() : s.foo(0, 0));
assert(x15296 == 2 + 3);

// CommaExp at the top of ExpStatement
x15296 = 0;
(s.foo(0, 0), voidCall15296());
assert(x15296 == 3 + 2);
}

// ----

struct File15296
{
struct Impl {}
Impl* _p;

pragma(inline, true)
~this() { _p = null; }

struct LockingTextWriter
{
pragma(inline, true)
this(ref File15296 f)
{
assert(f._p, "Attempting to write to closed File");
}
}

pragma(inline, true)
auto lockingTextWriter() { return LockingTextWriter(this); }

pragma(inline, true)
void write() { auto w = lockingTextWriter(); }

//pragma(inline, true)
static uint formattedWrite(Writer)(Writer w) { return 0; }

pragma(inline, true)
void writef() { formattedWrite(lockingTextWriter()); }
}

__gshared File15296 stdout15296 = {new File15296.Impl()};

pragma(inline, true)
@property File15296 trustedStdout15296() { return stdout15296; }

// ----
// reduced case from runnable/test34.d test34()

void test15296b()
{
// trustedStdout() returns a temporary File object. Its dtor call
// should be deferred till the end of expanded writef body statements.
trustedStdout15296().writef();
}

// ----
// reduced case from runnable/xtest46.d test136()

struct Perm15296c
{
this(byte[] input)
{
foreach (elem; input)
{
// if vthis.isDataseg() is true in expandInline,
// its edtor should not be called.
stdout15296.write();
}
}
}

void test15296c()
{
auto perm2 = Perm15296c([0, 1, 2]);
}

/**********************************/
Expand Down Expand Up @@ -1009,6 +1098,8 @@ int main()
test9785_3();
test15207();
test15296();
test15296b();
test15296c();

printf("Success\n");
return 0;
Expand Down

0 comments on commit 25453b6

Please sign in to comment.