Skip to content

Commit af4bae4

Browse files
committed
Implement most RVO cases in glue layer
1 parent ac92d7d commit af4bae4

5 files changed

Lines changed: 404 additions & 163 deletions

File tree

compiler/src/dmd/expressionsem.d

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,10 @@ bool isLvalue(Expression _this)
773773
* a typed storage. This basically elides a restricted subset of so-called
774774
* "pure" rvalues, i.e. expressions with no reference semantics.
775775
*
776+
* Please try to keep `dmd.glue.e2ir.toElemRVO()` in sync with this.
777+
* It is not destructive to fail to elide a copy, but it is always better
778+
* to stay consistent.
779+
*
776780
* Note: Please avoid using `checkMod` parameter because `canElideCopy()`
777781
* essentially defines a value category and should eventually be merged with
778782
* `isLvalue()` to return [isLvalue, allowEmplacement].
@@ -12329,7 +12333,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
1232912333
? cast(DotVarExp)ce.e1 : null;
1233012334
if (sd.ctor && ce && dve && dve.var.isCtorDeclaration() &&
1233112335
// https://issues.dlang.org/show_bug.cgi?id=19389
12332-
dve.e1.op != EXP.dotVariable &&
12336+
canElideCopy(ce, t1) &&
1233312337
e2y.type.implicitConvTo(t1))
1233412338
{
1233512339
/* Look for form of constructor call which is:
@@ -12441,22 +12445,9 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
1244112445
return;
1244212446
}
1244312447
}
12444-
else if (sd.hasMoveCtor && (!e2x.isCallExp() || e2x.rvalue) && !e2x.isStructLiteralExp())
12448+
else if (sd.hasMoveCtor && !canElideCopy(e2x, t1))
1244512449
{
1244612450
// #move
12447-
/* The !e2x.isCallExp() is because it is already an rvalue
12448-
and the move constructor is unnecessary:
12449-
struct S {
12450-
alias TT this;
12451-
long TT();
12452-
this(T)(int x) {}
12453-
this(S);
12454-
this(ref S);
12455-
~this();
12456-
}
12457-
S fun(ref S arg);
12458-
void test() { S st; fun(st); }
12459-
*/
1246012451
/* Rewrite as:
1246112452
* e1 = init, e1.moveCtor(e2);
1246212453
*/

compiler/src/dmd/glue/e2ir.d

Lines changed: 223 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2660,45 +2660,6 @@ elem* toElem(Expression e, ref IRState irs)
26602660
return setResult2(e);
26612661
}
26622662

2663-
/* This will work if we can distinguish an assignment from
2664-
* an initialization of the lvalue. It'll work if the latter.
2665-
* If the former, because of aliasing of the return value with
2666-
* function arguments, it'll fail.
2667-
*/
2668-
if (ae.op == EXP.construct)
2669-
{
2670-
if (CallExp ce = lastComma(ae.e2).isCallExp())
2671-
{
2672-
TypeFunction tf = cast(TypeFunction)ce.e1.type.toBasetype();
2673-
if (tf.ty == Tfunction && retStyle(tf, ce.f && ce.f.needThis()) == RET.stack)
2674-
{
2675-
elem* eh = el_una(OPaddr, TYnptr, e1);
2676-
elem* e = toElemRVO(ae.e2, eh, irs);
2677-
return setResult2(e);
2678-
}
2679-
2680-
/* Look for:
2681-
* v = structliteral.ctor(args)
2682-
* and have the structliteral write into v, rather than create a temporary
2683-
* and copy the temporary into v
2684-
*/
2685-
if (ae.e1.op == EXP.variable && ce.e1.op == EXP.dotVariable)
2686-
{
2687-
auto dve = cast(DotVarExp)ce.e1;
2688-
auto fd = dve.var.isFuncDeclaration();
2689-
if (fd && fd.isCtorDeclaration())
2690-
{
2691-
if (auto sle = dve.e1.isStructLiteralExp())
2692-
{
2693-
elem* eh = el_una(OPaddr, TYnptr, e1);
2694-
elem* e = toElemRVO(ae.e2, eh, irs);
2695-
return setResult2(e);
2696-
}
2697-
}
2698-
}
2699-
}
2700-
}
2701-
27022663
//if (ae.op == EXP.construct) printf("construct\n");
27032664
if (auto t1s = t1b.isTypeStruct())
27042665
{
@@ -2764,6 +2725,15 @@ elem* toElem(Expression e, ref IRState irs)
27642725
}
27652726
}
27662727

2728+
/* Implement:
2729+
* S struct = func()
2730+
*/
2731+
if (ae.op == EXP.construct && canElideCopy(ae.e2, ae.e1.type))
2732+
{
2733+
elem* e = toElemRVO(ae.e2, e1, irs);
2734+
return setResult2(e);
2735+
}
2736+
27672737
/* Implement:
27682738
* (struct = struct)
27692739
*/
@@ -2852,6 +2822,15 @@ elem* toElem(Expression e, ref IRState irs)
28522822
}
28532823
}
28542824

2825+
/* Implement:
2826+
* S[n] sarray = func()
2827+
*/
2828+
if (ae.op == EXP.construct && canElideCopy(ae.e2, ae.e1.type))
2829+
{
2830+
elem* e = toElemRVO(ae.e2, e1, irs);
2831+
return setResult2(e);
2832+
}
2833+
28552834
/* https://issues.dlang.org/show_bug.cgi?id=13661
28562835
* Even if the elements in rhs are all rvalues and
28572836
* don't have to call postblits, this assignment should call
@@ -4180,21 +4159,58 @@ elem* toElem(Expression e, ref IRState irs)
41804159
* e = Expression to convert
41814160
* ehidden = lvalue element to place the value in these forms:
41824161
* (OPvar TYnptr), which is a pointer to the storage
4183-
* (OPvar TYsarray/TYstruct), which is the target symbol
4184-
* (OPind TYsarray/TYstruct), which is the target location
4162+
* (OPvar TYarray/TYstruct), which is the target symbol
4163+
* (OPind TYarray/TYstruct), which is the target location
41854164
* irs = context
4165+
* offsetVar = the field to extract from e
41864166
* Returns:
4187-
* generated elem tree
4167+
* generated elem tree with type TYarray/TYstruct
41884168
*/
4189-
elem* toElemRVO(Expression e, elem* ehidden, ref IRState irs)
4169+
elem* toElemRVO(Expression e, elem* ehidden, ref IRState irs, VarDeclaration offsetVar = null)
41904170
{
4191-
assert(e.type.toBasetype().ty == Tstruct || e.type.toBasetype().ty == Tsarray);
4171+
/* RVO is only applicable to structs and static arrays.
4172+
* When extracting a single field, also reject unions.
4173+
*/
4174+
if (offsetVar)
4175+
{
4176+
assert(!offsetVar.overlapped);
4177+
assert(offsetVar.type.toBasetype().ty == Tstruct || offsetVar.type.toBasetype().ty == Tsarray);
4178+
}
4179+
else
4180+
{
4181+
assert(e.type.toBasetype().ty == Tstruct || e.type.toBasetype().ty == Tsarray);
4182+
}
4183+
4184+
/* There are cases where RVO appears to be possible in the frontend,
4185+
* but can't be done here. Generate a blit here and wait for bug reports,
4186+
* so frontend code can be fixed.
4187+
*/
4188+
elem* blitToDestination(Expression e)
4189+
{
4190+
if (offsetVar)
4191+
{
4192+
e = new DotVarExp(e.loc, e, offsetVar, false);
4193+
e.type = offsetVar.type;
4194+
}
4195+
4196+
elem* e1 = toElem(e, irs);
4197+
4198+
if (tybasic(e1.Ety) == TYnoreturn)
4199+
return e1;
4200+
4201+
// Generate (ehidden=e)
4202+
if (tybasic(ehidden.Ety) == TYnptr)
4203+
ehidden = el_una(OPind, e1.Ety, ehidden);
4204+
elem* ea = elAssign(ehidden, e1, null, e1.ET);
4205+
elem_setLoc(ea, e.loc);
4206+
return ea;
4207+
}
41924208

41934209
elem* doCommaRVO(CommaExp ce)
41944210
{
41954211
assert(ce.e1 && ce.e2);
41964212
elem* eleft = toElem(ce.e1, irs);
4197-
elem* eright = toElemRVO(ce.e2, ehidden, irs);
4213+
elem* eright = toElemRVO(ce.e2, ehidden, irs, offsetVar);
41984214
elem* e = el_combine(eleft, eright);
41994215
if (e)
42004216
elem_setLoc(e, ce.loc);
@@ -4207,15 +4223,15 @@ elem* toElemRVO(Expression e, elem* ehidden, ref IRState irs)
42074223
auto ctfecond = ce.econd.op == EXP.not ? (cast(NotExp)ce.econd).e1 : ce.econd;
42084224
if (auto ve = ctfecond.isVarExp())
42094225
if (ve.var && ve.var.ident == Id.ctfe)
4210-
return toElemRVO(ctfecond is ce.econd ? ce.e2 : ce.e1, ehidden, irs);
4226+
return toElemRVO(ctfecond is ce.econd ? ce.e2 : ce.e1, ehidden, irs, offsetVar);
42114227

42124228
elem* ec = toElem(ce.econd, irs);
42134229

4214-
elem* eleft = toElemRVO(ce.e1, ehidden, irs);
4230+
elem* eleft = toElemRVO(ce.e1, ehidden, irs, offsetVar);
42154231
if (irs.params.cov && ce.e1.loc.linnum)
42164232
eleft = el_combine(incUsageElem(irs, ce.e1.loc), eleft);
42174233

4218-
elem* eright = toElemRVO(ce.e2, el_copytree(ehidden), irs);
4234+
elem* eright = toElemRVO(ce.e2, el_copytree(ehidden), irs, offsetVar);
42194235
if (irs.params.cov && ce.e2.loc.linnum)
42204236
eright = el_combine(incUsageElem(irs, ce.e2.loc), eright);
42214237

@@ -4235,17 +4251,127 @@ elem* toElemRVO(Expression e, elem* ehidden, ref IRState irs)
42354251

42364252
elem* doCallRVO(CallExp ce)
42374253
{
4238-
return toElemCall(ce, irs, ehidden);
4254+
assert(!offsetVar);
4255+
4256+
FuncDeclaration fd;
4257+
4258+
if (auto dve = ce.e1.isDotVarExp())
4259+
fd = dve.var.isFuncDeclaration();
4260+
else
4261+
fd = ce.f;
4262+
4263+
Type t = ce.e1.type.toBasetype();
4264+
if (t.ty == Tdelegate)
4265+
t = t.nextOf();
4266+
4267+
if (fd && fd.isCtorDeclaration() ||
4268+
t.ty == Tfunction && retStyle(cast(TypeFunction)t, fd && fd.needThis()) == RET.stack)
4269+
{
4270+
// The hidden pointer must be a pointer by definition.
4271+
if (tybasic(ehidden.Ety) != TYnptr)
4272+
ehidden = el_una(OPaddr, TYnptr, ehidden);
4273+
4274+
return toElemCall(ce, irs, ehidden);
4275+
}
4276+
4277+
return blitToDestination(ce);
4278+
}
4279+
4280+
elem* doVariableRVO(VarExp ve)
4281+
{
4282+
assert(!offsetVar);
4283+
4284+
/* If ve.var is already pointing to the hidden pointer,
4285+
* replace it with ehidden.
4286+
*/
4287+
if (ehidden.Eoper == OPvar &&
4288+
ehidden.Voffset == 0 &&
4289+
ehidden.Vsym == toSymbol(ve.var))
4290+
{
4291+
if (tybasic(ehidden.Ety) == TYnptr)
4292+
{
4293+
ehidden = el_una(OPind, totym(ve.var.type), ehidden);
4294+
ehidden.ET = Type_toCtype(ve.var.type);
4295+
}
4296+
return ehidden;
4297+
}
4298+
4299+
/* Blit otherwise. This means a frontend-generated variable
4300+
* fails to account for RVO.
4301+
*/
4302+
return blitToDestination(ve);
4303+
}
4304+
4305+
elem* doDotVariableRVO(DotVarExp dve)
4306+
{
4307+
assert(!offsetVar);
4308+
4309+
if (auto vd = dve.var.isVarDeclaration())
4310+
if (!vd.overlapped)
4311+
return toElemRVO(dve.e1, ehidden, irs, vd);
4312+
4313+
return blitToDestination(dve);
42394314
}
42404315

42414316
elem* doStructLiteralRVO(StructLiteralExp sle)
42424317
{
4318+
if (offsetVar)
4319+
{
4320+
if (sle.useStaticInit)
4321+
{
4322+
// Generate (ehidden=*(&__init+offset))
4323+
elem* e1 = el_var(toInitializer(sle.sd));
4324+
e1 = addressElem(e1, sle.sd.type);
4325+
elem* eoffset = el_long(TYsize_t, offsetVar.offset);
4326+
e1 = el_bin(OPadd, TYnptr, e1, eoffset);
4327+
if (offsetVar.storage_class & (STC.out_ | STC.ref_))
4328+
e1 = el_una(OPind, TYnptr, e1);
4329+
e1 = el_una(OPind, totym(offsetVar.type), e1);
4330+
e1.ET = Type_toCtype(offsetVar.type);
4331+
if (tybasic(ehidden.Ety) == TYnptr)
4332+
ehidden = el_una(OPind, e1.Ety, ehidden);
4333+
elem* ea = elAssign(ehidden, e1, null, e1.ET);
4334+
elem_setLoc(ea, sle.loc);
4335+
return ea;
4336+
}
4337+
4338+
/* Find the corresponding initializer for offsetVar,
4339+
* and fold S(f(), g(), h()).field2 to g().
4340+
* Generate (f(), ehidden=g(), h(), ehidden).
4341+
*/
4342+
elem* e1, ex;
4343+
bool match;
4344+
4345+
foreach (i, element; *sle.elements)
4346+
{
4347+
if (offsetVar == sle.sd.fields[i])
4348+
{
4349+
if (canElideCopy(element, element.type))
4350+
ex = toElemRVO(element, ehidden, irs);
4351+
else
4352+
ex = blitToDestination(element);
4353+
match = true;
4354+
}
4355+
else
4356+
ex = toElem(element, irs);
4357+
e1 = el_combine(e1, ex);
4358+
}
4359+
4360+
assert(match);
4361+
ehidden = el_copytree(ehidden);
4362+
if (tybasic(ehidden.Ety) == TYnptr)
4363+
ehidden = el_una(OPind, e1.Ety, ehidden);
4364+
e1 = el_combine(e1, ehidden);
4365+
elem_setLoc(e1, sle.loc);
4366+
return e1;
4367+
}
4368+
42434369
if (ehidden.Eoper == OPvar && ehidden.Voffset == 0)
42444370
return toElemStructLit(sle, irs, EXP.construct, ehidden.Vsym, true);
42454371

42464372
// Generate (tmp=&ehidden, *tmp=struct)
42474373
if (tybasic(ehidden.Ety) != TYnptr)
4248-
ehidden = addressElem(ehidden, sle.sd.type.toBasetype());
4374+
ehidden = addressElem(ehidden, sle.sd.type);
42494375
Symbol* stmp = symbol_genauto(TYnptr);
42504376
elem* e1 = el_bin(OPeq, TYnptr, el_var(stmp), ehidden);
42514377
elem* es = toElemStructLit(sle, irs, EXP.construct, stmp, true);
@@ -4254,14 +4380,54 @@ elem* toElemRVO(Expression e, elem* ehidden, ref IRState irs)
42544380
return e1;
42554381
}
42564382

4257-
// Please keep in sync with dmd.expressionsem.canElideCopy()
4258-
switch (e.op)
4383+
/* For field access, transform:
4384+
*
4385+
* [1] v = (exp1, cond ? exp2 : exp3).field
4386+
* => (exp1, cond ? (v = exp2.field) : (v = exp3.field))
4387+
*
4388+
* [2] v = S(f(), field: T()).field
4389+
* => (f(), v = T())
4390+
*
4391+
* TODO: Optimally, we should be able to optimize
4392+
* v = S(f(), v1: T(g(), v2: R()), h()).v1.v2
4393+
* => (f(), g(), v = R(), h(), v)
4394+
* but it would take extra complexity to keep evaluation order unchanged.
4395+
*
4396+
* For initialization, transform:
4397+
*
4398+
* [1] v = (exp1, cond ? exp2 : exp3)
4399+
* => (exp1, cond ? (v = exp2) : (v = exp3))
4400+
*
4401+
* [2] v = func()
4402+
* => pass the address of v to func(), if func() is ctor or returns on stack
4403+
*
4404+
* [3] v = v
4405+
* => v
4406+
*
4407+
* NOTE: Please keep in sync with dmd.expressionsem.canElideCopy().
4408+
*/
4409+
if (offsetVar)
4410+
{
4411+
switch (e.op)
4412+
{
4413+
case EXP.comma: return doCommaRVO(e.isCommaExp());
4414+
case EXP.question: return doCondRVO(e.isCondExp());
4415+
case EXP.structLiteral: return doStructLiteralRVO(e.isStructLiteralExp());
4416+
default: return blitToDestination(e);
4417+
}
4418+
}
4419+
else
42594420
{
4260-
case EXP.comma: return doCommaRVO(e.isCommaExp());
4261-
case EXP.question: return doCondRVO(e.isCondExp());
4262-
case EXP.call: return doCallRVO(e.isCallExp());
4263-
case EXP.structLiteral: return doStructLiteralRVO(e.isStructLiteralExp());
4264-
default: assert(0);
4421+
switch (e.op)
4422+
{
4423+
case EXP.comma: return doCommaRVO(e.isCommaExp());
4424+
case EXP.question: return doCondRVO(e.isCondExp());
4425+
case EXP.call: return doCallRVO(e.isCallExp());
4426+
case EXP.variable: return doVariableRVO(e.isVarExp());
4427+
case EXP.dotVariable: return doDotVariableRVO(e.isDotVarExp());
4428+
case EXP.structLiteral: return doStructLiteralRVO(e.isStructLiteralExp());
4429+
default: assert(0);
4430+
}
42654431
}
42664432
}
42674433

0 commit comments

Comments
 (0)