dmd icon indicating copy to clipboard operation
dmd copied to clipboard

[REG2.111] Colliding lambda symbol mangles

Open kinke opened this issue 5 months ago • 3 comments

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
}

kinke avatar May 01 '25 13:05 kinke