dmd
dmd copied to clipboard
[REG2.111] Colliding lambda symbol mangles
Since v2.111, DMD might emit multiple different lambdas with the same mangle. This isn't caught by DMD, but LDC emits a warning at least if the colliding lambdas happen to be codegen'd into the same object file. We hit such a case at Symmetry.
After a long dustmite session and simplifying stuff manually, the ~220-lines testcase still depends a little bit on Phobos (but just std.meta and std.traits - edit: oh, and std.datetime):
import std.meta : anySatisfy, staticMap, AliasSeq, Filter;
import std.traits : isCallable, FunctionTypeOf, Parameters, ReturnType;
struct ParamTypeMaps(maps...) {
alias typeMaps = maps;
}
struct TypeMaps(ParamMaps) {
alias paramMaps = ParamMaps.typeMaps;
}
template findParamMap(TargetType, paramMaps...) {
alias paramMap = findParamMapSingle!(TargetType, paramMaps);
static if (paramMap.length)
alias findParamMap = paramMap;
else
alias findParamMap = resolveParamTypeMap!TargetType;
}
template findParamMapSingle(TargetType, paramMaps...) {
enum returnsTargetType(alias paramMap) = is(ReturnType!paramMap == TargetType);
alias findParamMapSingle = Filter!(returnsTargetType, paramMaps);
}
alias resolveParamTypeMap(TargetType) = findParamMapSingle!TargetType;
enum hasParamMap(TargetType, paramMaps...) = findParamMap!(TargetType, paramMaps).length;
template mapParam(alias fun, size_t idx, TMs) {
alias Param = BetterParams!fun[idx .. idx + 1];
alias paramMap = findParamMap!(Param, TMs.paramMaps);
static if (paramMap.length) {
enum paramName = __traits(identifier, Param);
mixin("
\t\t\tParam[0] mapParam(@getParamUDAs!(fun, idx) @getParamUDAs!(paramMap, 0) Parameters!paramMap[0] " ~ paramName ~ ") {
\t\t\t\treturn paramMap[0](" ~ paramName ~ ");
\t\t\t}
\t\t");
}
auto mapParam(Param _args) {
return _args[0];
}
}
template needsTranslation(alias fun, TMs) {
enum needsParamMap(ParamT) = hasParamMap!(ParamT, TMs.paramMaps);
enum needsTranslation = anySatisfy!(needsParamMap, Parameters!fun);
}
template translateFunction(alias fun, TMs) if (needsTranslation!(fun, TMs)) {
enum funName = __traits(identifier, fun);
alias PS = BetterParams!fun;
string translateStr() {
string[] lambdaArgs;
string[] mappedArgs;
foreach (i, param; PS) {
enum paramName = __traits(identifier, PS[i .. i + 1]);
lambdaArgs ~= "BetterParams!(mapParam!(fun, " ~ i.stringof ~ ", TMs))";
mappedArgs ~= "mapParam!(fun, " ~ i.stringof ~ ", TMs)(" ~ paramName ~ ")";
}
string ret = "auto " ~ funName ~ "(";
foreach (a; lambdaArgs)
ret ~= a ~ ", ";
ret ~= ")\n{\n";
ret ~= "\tfun(";
foreach (a; mappedArgs)
ret ~= a ~ ", ";
ret ~= ");\n";
ret ~= "};";
ret ~= "\nalias translateFunction = " ~ funName ~ ";";
return ret;
}
mixin(translateStr);
}
template translateFunction(alias fun, TMs) if (!needsTranslation!(fun, TMs)) {
alias translateFunction = fun;
}
template getParamUDAs(alias fun, size_t paramIdx) {
alias P = BetterParams!fun[paramIdx .. paramIdx + 1];
alias getParamUDAs = __traits(getAttributes, P);
}
template getSILtype(alias fn, size_t paramIdx) {
enum isSILtype() = false;
alias types = Filter!(isSILtype, getParamUDAs!(fn, paramIdx));
}
template BetterParams(func...) {
static if (is(FunctionTypeOf!func P == __parameters))
alias BetterParams = P;
}
template paramTypeNameOverride(alias fn, size_t paramIdx) {
alias silType = getSILtype!(fn, paramIdx);
enum paramTypeNameOverride = "";
}
template ParameterNames(alias fun) {
alias Ps = BetterParams!fun;
enum paramName(size_t i) = __traits(identifier, Ps);
alias ParameterNames = staticMap!(paramName, staticIota!(0, Ps.length));
}
void createFunctionOverloadImpl(alias handler)() {
alias paramNames = ParameterNames!handler;
alias PS = Parameters!handler;
foreach (i, argName; paramNames) {
string typeNameOverride = paramTypeNameOverride!(handler, i);
auto dg = () => convertParam!(PS[i])();
}
}
template isPubliclyVisible(alias sym) {
enum v = __traits(getVisibility, sym);
enum isPubliclyVisible = v == "public" ;
}
struct Handlers {
void registerType(T, TMs)() {
new Accessor!(T, TMs);
}
}
template MethodSpec(T, string n, size_t i = 0) {
alias Type = T;
enum name = n;
enum overload = i;
}
template getMethod(T, string name, size_t overload) {
alias getMethod = __traits(getOverloads, T, name, true)[overload];
}
alias getMethod(alias method) = getMethod!(method.Type, method.name, method.overload);
auto callMethod(alias method)(BetterParams!(getMethod!method)) {}
template isBindableMethod(alias method) {
alias resolvedMethod = getMethod!method;
enum isBindableMethod = isPubliclyVisible!resolvedMethod;
}
template AllBindableOverloads(T, string fn) {
alias member = __traits(getMember, T, fn);
static if (isCallable!member)
alias AllBindableOverloads = Filter!(isBindableMethod, MethodSpec!(T, fn));
else
alias AllBindableOverloads = AliasSeq!();
}
template OverloadSetSpec(Os...) {
alias Overloads = Os;
}
template BindableOverloadSets(T, string fn) {
alias overloads = AllBindableOverloads!(T, fn);
static if (overloads.length) {
alias BindableOverloadSets = OverloadSetSpec!overloads;
} else {
template makeOverloadSet() {}
alias BindableOverloadSets = staticMap!makeOverloadSet;
}
}
template AllBindableOverloadSets(T) {
alias bindableOverloadSets(string fn) = BindableOverloadSets!(T, fn);
alias AllBindableOverloadSets = staticMap!(bindableOverloadSets, __traits(allMembers, T));
}
class Accessor(T, TMs) {
this() {
foreach (overloadSet; AllBindableOverloadSets!T) {
foreach (method; overloadSet.Overloads) {
alias translatedMethod = translateFunction!(callMethod!method, TMs);
alias TranslatedPs = BetterParams!translatedMethod;
createFunctionOverloadImpl!((TranslatedPs _args) {})();
}
}
}
}
template getOverloads(alias symbol) {
alias parent = __traits(parent, symbol);
enum name = __traits(identifier, symbol);
alias getOverloads = __traits(getOverloads, parent, name);
}
bool convertParam(DesiredParamT)() {
return false;
}
template staticIota(size_t begin, size_t len) {
static if (len == 0) {
alias staticIota = AliasSeq!();
} else static if (len == 1) {
alias staticIota = AliasSeq!(begin);
} else static if (len == 2) {
alias staticIota = AliasSeq!(begin, begin + 1);
} else {
alias staticIota =
AliasSeq!(staticIota!(begin, len / 2),
staticIota!(begin + len / 2, len - len / 2));
}
}
ubyte integerToUbyte(int ) {
return 0;
}
void registerHandlersDateTime(Handlers handlers) {
import std.datetime : Date;
alias PMs = ParamTypeMaps!integerToUbyte;
alias TMs = TypeMaps!PMs;
handlers.registerType!(Date, TMs);
}
With v2.110, we have a single definition for a particular lambda symbol:
$ ~/dlang/dmd-2.110.0/linux/bin64/dmd -c lang.d
$ objdump -t lang.o | grep _D4lang__T26createFunctionOverloadImplS_DQBn__T8AccessorTS3std8datetime4date4DateTSQDd__T8TypeMapsTSQDu__T13ParamTypeMapsS_DQEs14integerToUbyteFiZhZQBqZQClZQEf6__ctorMFZ20__lambda_L182_C45_14FNaNbNiNfsiEQFpQFoQFi9DayOfWeekZvZQIhFZ17__lambda_L115_C19FNaNbNiNfZb
0000000000000000 l d .text._D4lang__T26createFunctionOverloadImplS_DQBn__T8AccessorTS3std8datetime4date4DateTSQDd__T8TypeMapsTSQDu__T13ParamTypeMapsS_DQEs14integerToUbyteFiZhZQBqZQClZQEf6__ctorMFZ20__lambda_L182_C45_14FNaNbNiNfsiEQFpQFoQFi9DayOfWeekZvZQIhFZ17__lambda_L115_C19FNaNbNiNfZb 0000000000000000 .text._D4lang__T26createFunctionOverloadImplS_DQBn__T8AccessorTS3std8datetime4date4DateTSQDd__T8TypeMapsTSQDu__T13ParamTypeMapsS_DQEs14integerToUbyteFiZhZQBqZQClZQEf6__ctorMFZ20__lambda_L182_C45_14FNaNbNiNfsiEQFpQFoQFi9DayOfWeekZvZQIhFZ17__lambda_L115_C19FNaNbNiNfZb
0000000000000000 w F .text._D4lang__T26createFunctionOverloadImplS_DQBn__T8AccessorTS3std8datetime4date4DateTSQDd__T8TypeMapsTSQDu__T13ParamTypeMapsS_DQEs14integerToUbyteFiZhZQBqZQClZQEf6__ctorMFZ20__lambda_L182_C45_14FNaNbNiNfsiEQFpQFoQFi9DayOfWeekZvZQIhFZ17__lambda_L115_C19FNaNbNiNfZb 000000000000000b _D4lang__T26createFunctionOverloadImplS_DQBn__T8AccessorTS3std8datetime4date4DateTSQDd__T8TypeMapsTSQDu__T13ParamTypeMapsS_DQEs14integerToUbyteFiZhZQBqZQClZQEf6__ctorMFZ20__lambda_L182_C45_14FNaNbNiNfsiEQFpQFoQFi9DayOfWeekZvZQIhFZ17__lambda_L115_C19FNaNbNiNfZb
With v2.111, we have 2 definitions in that single object file:
$ ~/dlang/dmd-2.111.0/linux/bin64/dmd -c lang.d
$ objdump -t lang.o | grep _D4lang__T26createFunctionOverloadImplS_DQBn__T8AccessorTS3std8datetime4date4DateTSQDd__T8TypeMapsTSQDu__T13ParamTypeMapsS_DQEs14integerToUbyteFiZhZQBqZQClZQEf6__ctorMFZ20__lambda_L182_C45_14FNaNbNiNfsiEQFpQFoQFi9DayOfWeekZvZQIhFZ17__lambda_L115_C19FNaNbNiNfZb
0000000000000000 l d .text._D4lang__T26createFunctionOverloadImplS_DQBn__T8AccessorTS3std8datetime4date4DateTSQDd__T8TypeMapsTSQDu__T13ParamTypeMapsS_DQEs14integerToUbyteFiZhZQBqZQClZQEf6__ctorMFZ20__lambda_L182_C45_14FNaNbNiNfsiEQFpQFoQFi9DayOfWeekZvZQIhFZ17__lambda_L115_C19FNaNbNiNfZb 0000000000000000 .text._D4lang__T26createFunctionOverloadImplS_DQBn__T8AccessorTS3std8datetime4date4DateTSQDd__T8TypeMapsTSQDu__T13ParamTypeMapsS_DQEs14integerToUbyteFiZhZQBqZQClZQEf6__ctorMFZ20__lambda_L182_C45_14FNaNbNiNfsiEQFpQFoQFi9DayOfWeekZvZQIhFZ17__lambda_L115_C19FNaNbNiNfZb
0000000000000000 w F .text._D4lang__T26createFunctionOverloadImplS_DQBn__T8AccessorTS3std8datetime4date4DateTSQDd__T8TypeMapsTSQDu__T13ParamTypeMapsS_DQEs14integerToUbyteFiZhZQBqZQClZQEf6__ctorMFZ20__lambda_L182_C45_14FNaNbNiNfsiEQFpQFoQFi9DayOfWeekZvZQIhFZ17__lambda_L115_C19FNaNbNiNfZb 000000000000000b _D4lang__T26createFunctionOverloadImplS_DQBn__T8AccessorTS3std8datetime4date4DateTSQDd__T8TypeMapsTSQDu__T13ParamTypeMapsS_DQEs14integerToUbyteFiZhZQBqZQClZQEf6__ctorMFZ20__lambda_L182_C45_14FNaNbNiNfsiEQFpQFoQFi9DayOfWeekZvZQIhFZ17__lambda_L115_C19FNaNbNiNfZb
0000000000000000 w F .text._D4lang__T26createFunctionOverloadImplS_DQBn__T8AccessorTS3std8datetime4date4DateTSQDd__T8TypeMapsTSQDu__T13ParamTypeMapsS_DQEs14integerToUbyteFiZhZQBqZQClZQEf6__ctorMFZ20__lambda_L182_C45_14FNaNbNiNfsiEQFpQFoQFi9DayOfWeekZvZQIhFZ17__lambda_L115_C19FNaNbNiNfZb 000000000000000b _D4lang__T26createFunctionOverloadImplS_DQBn__T8AccessorTS3std8datetime4date4DateTSQDd__T8TypeMapsTSQDu__T13ParamTypeMapsS_DQEs14integerToUbyteFiZhZQBqZQClZQEf6__ctorMFZ20__lambda_L182_C45_14FNaNbNiNfsiEQFpQFoQFi9DayOfWeekZvZQIhFZ17__lambda_L115_C19FNaNbNiNfZb
This is a horrible bug. E.g., faking this:
int foo() { return 1; }
pragma(mangle, foo.mangleof)
int foo2() { return 2; }
void main() {
assert(foo() == 1);
assert(foo2() == 2); // fails - the first `foo()` 'wins' - the linker seems to concatenate the sections, so the code for `foo2` is still there but never reached
}