Skip to content

Commit f518a22

Browse files
committed
Revise ABI-specific part of -preview=in
This is the promised follow-up on dlang#11000 and makes DMD exploit the specifics of the few supported ABIs (Win64, SysV x86_64, 32-bit x86). It also almost perfectly matches the proposed LDC implementation in ldc-developers/ldc#3578 (just a minor divergence for Win64 and dynamic arrays, but in that point the LDC and DMD Win64 ABI diverges in general).
1 parent 6f8b9bf commit f518a22

File tree

6 files changed

+116
-75
lines changed

6 files changed

+116
-75
lines changed

src/dmd/frontend.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -6472,7 +6472,7 @@ struct Target
64726472
TypeTuple* toArgTypes(Type* t);
64736473
bool isReturnOnStack(TypeFunction* tf, bool needsThis);
64746474
uint64_t parameterSize(const Loc& loc, Type* t);
6475-
void applyInRefParams(TypeFunction* tf);
6475+
bool preferPassByRef(Type* t);
64766476
BEGIN_ENUM(TargetInfoKeys, TARGETINFOKEYS, targetinfokeys)
64776477
ENUM_KEY(int32_t, cppRuntimeLibrary, 0, TargetInfoKeys, TARGETINFOKEYS, targetinfokeys, TIK)
64786478
ENUM_KEY(int32_t, cppStd, 1, TargetInfoKeys, TARGETINFOKEYS, targetinfokeys, TIK)

src/dmd/target.d

+41-47
Original file line numberDiff line numberDiff line change
@@ -772,60 +772,54 @@ extern (C++) struct Target
772772
}
773773

774774
/**
775-
* Determine which `in` parameters needs to be passed by `ref`
776-
*
777-
* Called from `TypeFunction` semantic with the full function type.
778-
* This routine must iterate over parameters, and may set `STC.ref_`
779-
* for any parameter which already have `STC.in_`.
780-
* This hook must never set `STC.ref_` if the parameter is not `STC.in_`,
781-
* nor should it ever change anything else.
782-
*
783-
* This hook will not be called when `-preview=in` wasn't passed to the
784-
* frontend, hence it needs not care about `global.params.previewIn`.
785-
*
775+
* Decides whether an `in` parameter of the specified POD type is to be
776+
* passed by reference or by value. To be used with `-preview=in` only!
786777
* Params:
787-
* tf = Type of the function to inspect. The type will have its
788-
* parameter types semantically resolved, however other attributes
789-
* (return type, `@safe` / `nothrow`, etc...) must not be used.
778+
* t = type of the `in` parameter, must be a POD
779+
* Returns:
780+
* `true` if the `in` parameter is to be passed by reference
790781
*/
791-
extern(C++) void applyInRefParams (TypeFunction tf)
782+
extern (C++) bool preferPassByRef(Type t)
792783
{
793-
foreach (_idx, p; tf.parameterList)
784+
const size = t.size();
785+
if (global.params.is64bit)
794786
{
795-
// Ignore non-`in` or already-`ref` parameters
796-
if ((p.storageClass & (STC.in_ | STC.ref_)) != STC.in_)
797-
continue;
798-
799-
assert(p.type !is null);
800-
// If it has a copy constructor / destructor / postblit,
801-
// it is always by ref
802-
if (p.type.needsDestruction() || p.type.needsCopyOrPostblit())
803-
p.storageClass |= STC.ref_;
804-
// If the type can't be copied, always by `ref`
805-
else if (!p.type.isCopyable())
806-
p.storageClass |= STC.ref_;
807-
// The Win64 ABI requires x87 real to be passed by ref
808-
else if (global.params.isWindows && global.params.is64bit &&
809-
p.type.ty == Tfloat80)
810-
p.storageClass |= STC.ref_;
811-
812-
// If it's a dynamic array, use the value type as it
813-
// allows covariance between `in char[]` and `scope const(char)[]`
814-
// The same reasoning applies to pointers and classes,
815-
// but that is handled by the `(sz > 8)` below.
816-
else if (p.type.ty == Tarray)
817-
continue;
818-
// Pass delegates by value to allow covariance
819-
// Function pointers are a single pointers and handled below.
820-
else if (p.type.ty == Tdelegate)
821-
continue;
822-
else
787+
if (global.params.isWindows)
788+
{
789+
// If size is larger than 8 or not a power-of-2, the Win64 ABI
790+
// would require a hidden reference anyway.
791+
return size > 8
792+
|| (size > 0 && (size & (size - 1)) != 0);
793+
}
794+
else // SysV x86_64 ABI
823795
{
824-
const sz = p.type.size();
825-
if (global.params.is64bit ? (sz > 16) : (sz > 8))
826-
p.storageClass |= STC.ref_;
796+
// Prefer a ref if the POD cannot be passed in registers, i.e.,
797+
// would be passed on the stack, *and* the size is > 16.
798+
if (size <= 16)
799+
return false;
800+
801+
TypeTuple getArgTypes()
802+
{
803+
import dmd.aggregate : Sizeok;
804+
if (auto ts = t.toBasetype().isTypeStruct())
805+
{
806+
auto sd = ts.sym;
807+
assert(sd.sizeok == Sizeok.done);
808+
return sd.argTypes;
809+
}
810+
return toArgTypes(t);
811+
}
812+
813+
TypeTuple argTypes = getArgTypes();
814+
assert(argTypes !is null, "size == 0 should already be handled");
815+
return argTypes.arguments.length == 0; // cannot be passed in registers
827816
}
828817
}
818+
else // 32-bit x86 ABI
819+
{
820+
// Prefer a ref if the size is > 2 machine words.
821+
return size > 8;
822+
}
829823
}
830824

831825
// this guarantees `getTargetInfo` and `allTargetInfos` remain in sync

src/dmd/target.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ struct Target
108108
TypeTuple *toArgTypes(Type *t);
109109
bool isReturnOnStack(TypeFunction *tf, bool needsThis);
110110
d_uns64 parameterSize(const Loc& loc, Type *t);
111-
void applyInRefParams(TypeFunction *tf);
111+
bool preferPassByRef(Type *t);
112112
Expression *getTargetInfo(const char* name, const Loc& loc);
113113
};
114114

src/dmd/typesem.d

+9-5
Original file line numberDiff line numberDiff line change
@@ -1484,12 +1484,16 @@ extern(C++) Type typeSemantic(Type type, const ref Loc loc, Scope* sc)
14841484

14851485
// Remove redundant storage classes for type, they are already applied
14861486
fparam.storageClass &= ~(STC.TYPECTOR);
1487-
}
14881487

1489-
// Now that we're done processing the types of parameters,
1490-
// apply `STC.ref` where necessary
1491-
if (global.params.previewIn)
1492-
target.applyInRefParams(tf);
1488+
// -preview=in: add `ref` storage class to suited `in` params
1489+
if (global.params.previewIn && (fparam.storageClass & (STC.in_ | STC.ref_)) == STC.in_)
1490+
{
1491+
auto ts = t.baseElemOf().isTypeStruct();
1492+
const isPOD = !ts || ts.sym.isPOD();
1493+
if (!isPOD || target.preferPassByRef(t))
1494+
fparam.storageClass |= STC.ref_;
1495+
}
1496+
}
14931497

14941498
// Now that we completed semantic for the argument types,
14951499
// run semantic on their default values,

test/compilable/previewin.d

+44-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* REQUIRED_ARGS: -preview=dip1000 -preview=in
1+
/* REQUIRED_ARGS: -preview=dip1000 -preview=in -mcpu=native
22
*/
33

44
import core.stdc.time;
@@ -64,3 +64,46 @@ void checkTemporary()
6464
LError:
6565
assert(0);
6666
}
67+
68+
69+
// Some ABI-specific tests:
70+
71+
version (Win64)
72+
{
73+
void checkReal(in real p)
74+
{
75+
// ref for x87 real, value for double-precision real
76+
static assert(__traits(isRef, p) == (real.sizeof > 8));
77+
}
78+
79+
struct RGB { ubyte r, g, b; }
80+
void checkNonPowerOf2(in RGB p)
81+
{
82+
static assert(__traits(isRef, p));
83+
}
84+
}
85+
else version (X86_64) // Posix x86_64
86+
{
87+
struct Empty {} // 1 dummy byte passed on the stack
88+
void checkEmptyStruct(in Empty p)
89+
{
90+
static assert(!__traits(isRef, p));
91+
}
92+
93+
static if (is(__vector(double[4])))
94+
{
95+
struct AvxVectorWrapper { __vector(double[4]) a; } // 256 bits
96+
void checkAvxVector(in AvxVectorWrapper p)
97+
{
98+
static assert(!__traits(isRef, p));
99+
}
100+
}
101+
}
102+
else version (AArch64)
103+
{
104+
alias HVA = __vector(float[4])[4]; // can be passed in 4 vector registers
105+
void checkHVA(in HVA p)
106+
{
107+
static assert(!__traits(isRef, p));
108+
}
109+
}

test/runnable/previewin.d

+20-20
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ void testWithAllAttributes() @safe pure nothrow @nogc
1313

1414
// rvalues
1515
testin1(42);
16-
testin2([42, ulong.max, ulong.min, 42UL]);
16+
testin2((ulong[64]).init);
1717
testin3(ValueT(42));
1818
testin3(RefT(42));
19-
testin4([ValueT(0), ValueT(1), ValueT(2), ValueT(3),
20-
ValueT(4), ValueT(5), ValueT(6), ValueT(7)]);
19+
testin4((ValueT[64]).init);
2120
testin4([RefT(42), RefT(84), RefT(126), RefT(4)]);
2221
testin5(NonCopyable(true));
2322
testin6(WithPostblit(true));
@@ -27,14 +26,14 @@ void testWithAllAttributes() @safe pure nothrow @nogc
2726
isTestOver = false;
2827

2928
// lvalues
30-
uint a1;
31-
ulong[4] a2;
32-
ValueT a3;
33-
ValueT[8] a4;
34-
RefT a5;
35-
RefT[4] a6;
29+
uint a1;
30+
ulong[64] a2;
31+
ValueT a3;
32+
ValueT[64] a4;
33+
RefT a5;
34+
RefT[4] a6;
3635
NonCopyable a7 = NonCopyable(true);
37-
WithPostblit a8;
36+
WithPostblit a8;
3837
WithCopyCtor a9;
3938
WithDtor a10 = WithDtor(&isTestOver);
4039

@@ -118,7 +117,7 @@ void testForeach() @safe pure
118117
}
119118

120119
struct ValueT { int value; }
121-
struct RefT { ubyte[64] value; }
120+
struct RefT { ulong[64] value; }
122121

123122
struct NonCopyable
124123
{
@@ -155,25 +154,26 @@ struct WithDtor
155154
@safe pure nothrow @nogc:
156155

157156
// By value
158-
void testin1(in uint p) {}
157+
void testin1(in uint p) { static assert(!__traits(isRef, p)); }
159158
// By ref because of size
160-
void testin2(in ulong[4] p) {}
159+
void testin2(in ulong[64] p) { static assert(__traits(isRef, p)); }
161160
// By value or ref depending on size
162-
void testin3(in ValueT p) {}
163-
void testin3(in RefT p) {}
161+
void testin3(in ValueT p) { static assert(!__traits(isRef, p)); }
162+
void testin3(in RefT p) { static assert(__traits(isRef, p)); }
164163
// By ref because of size
165-
void testin4(in ValueT[8] p) {}
166-
void testin4(in RefT[4] p) {}
164+
void testin4(in ValueT[64] p) { static assert(__traits(isRef, p)); }
165+
void testin4(in RefT[4] p) { static assert(__traits(isRef, p)); }
167166

168167
// By ref because of non-copyability
169-
void testin5(in NonCopyable noncopy) {}
168+
void testin5(in NonCopyable noncopy) { static assert(__traits(isRef, noncopy)); }
170169
// By ref because of postblit
171-
void testin6(in WithPostblit withposblit) {}
170+
void testin6(in WithPostblit withpostblit) { static assert(__traits(isRef, withpostblit)); }
172171
// By ref because of copy ctor
173-
void testin7(in WithCopyCtor withcopy) {}
172+
void testin7(in WithCopyCtor withcopy) { static assert(__traits(isRef, withcopy)); }
174173
// By ref because of dtor
175174
void testin8(in WithDtor withdtor, scope bool* isTestOver)
176175
{
176+
static assert(__traits(isRef, withdtor));
177177
if (isTestOver)
178178
*isTestOver = true;
179179
}

0 commit comments

Comments
 (0)