#include "CPlusPlusMangle.h"

#include <map>

#include "ExternFuncArgument.h"
#include "Function.h"
#include "IR.h"
#include "IROperator.h"
#include "Type.h"

/** \file Support for creating C++ mangled function names from a type signature. */

/** \note For Itanium C++ ABI, there is a specification here:
 *     https://mentorembedded.github.io/cxx-abi/abi.html
 * There is also useful info here:
 *     http://www.agner.org/optimize/calling_conventions.pdf
 */

namespace Halide {

namespace Internal {

namespace {

// Used in both Windows and Itanium manglers to track pieces of a type name
// in both their final form in the output and their canonical substituted form.
struct MangledNamePart {
    std::string full_name;
    std::string with_substitutions;

    MangledNamePart() = default;
    MangledNamePart(const std::string &mangled)
        : full_name(mangled), with_substitutions(mangled) {
    }
    MangledNamePart(const char *mangled)
        : full_name(mangled), with_substitutions(mangled) {
    }
};

Type non_null_void_star_type() {
    static halide_handle_cplusplus_type t(halide_handle_cplusplus_type(
        halide_cplusplus_type_name(halide_cplusplus_type_name::Simple, "void"),
        {}, {}, {halide_handle_cplusplus_type::Pointer}));
    return Handle(1, &t);
}

}  // namespace

namespace WindowsMangling {

struct PreviousDeclarations {
    std::map<std::string, int> prev_types;
    std::map<std::string, int> prev_names;

    std::string check_and_enter(std::map<std::string, int> &table, const std::string &name, const std::string &full) {
        int sub = -1;
        if (table.size() >= 10) {
            auto i = table.find(name);
            if (i != table.end()) {
                sub = i->second;
            }
        } else {
            auto insert_result = table.insert({name, table.size()});
            if (!insert_result.second) {
                sub = insert_result.first->second;
            }
        }
        if (sub != -1) {
            return std::string(1, (char)('0' + sub));
        } else {
            return full;
        }
    }

    std::string check_and_enter_type(const MangledNamePart &mangled) {
        if (mangled.full_name.size() < 2) {
            return mangled.full_name;
        }
        return check_and_enter(prev_types, mangled.full_name, mangled.with_substitutions);
    }

    std::string check_and_enter_name(const std::string &name) {
        return check_and_enter(prev_names, name, name + "@");
    }
};

std::string simple_type_to_mangle_char(const std::string &type_name, const Target &target) {
    if (type_name == "void") {
        return "X";
    } else if (type_name == "bool") {
        return "_N";
    } else if (type_name == "char") {
        return "D";
    }
    if (type_name == "int8_t") {
        return "C";
    } else if (type_name == "uint8_t") {
        return "E";
    } else if (type_name == "int16_t") {
        return "F";
    } else if (type_name == "uint16_t") {
        return "G";
    } else if (type_name == "int32_t") {
        return "H";
    } else if (type_name == "uint32_t") {
        return "I";
    } else if (type_name == "int64_t") {
        return "_J";
    } else if (type_name == "uint64_t") {
        return "_K";
    } else if (type_name == "float") {
        return "M";
    } else if (type_name == "double") {
        return "N";
    }
    user_error << "Unknown type name: " << type_name << "\n";
    return "";
}

std::string one_qualifier_set(bool is_const, bool is_volatile, bool is_restrict, bool is_pointer_target, const std::string &base_mode) {
    if (is_const && is_volatile) {
        return (is_pointer_target ? ("S" + base_mode) : "D");
    } else if (is_const) {
        return (is_pointer_target ? ("Q" + base_mode) : "B");
    } else if (is_volatile) {
        return (is_pointer_target ? ("R" + base_mode) : "C");
    } else if (is_restrict && is_pointer_target) {
        return ("P" + base_mode + "I");
    } else {
        return (is_pointer_target ? ("P" + base_mode) : "A");
    }
}

struct QualsState {
    bool last_is_pointer{false};

    const Type &type;
    const std::string base_mode;
    std::string result;

    bool finished{false};

    QualsState(const Type &type, const std::string &base_mode)
        : type(type), base_mode(base_mode) {
    }

    void handle_modifier(uint8_t modifier) {
        bool is_pointer = (modifier & halide_handle_cplusplus_type::Pointer) != 0;
        bool last_is_const = (modifier & halide_handle_cplusplus_type::Const) != 0;
        bool last_is_volatile = (modifier & halide_handle_cplusplus_type::Volatile) != 0;
        bool last_is_restrict = (modifier & halide_handle_cplusplus_type::Restrict) != 0;

        if (finished ||
            (!is_pointer && !last_is_pointer &&
             type.handle_type->reference_type == halide_handle_cplusplus_type::NotReference)) {
            finished = true;
            return;
        }

        result = one_qualifier_set(last_is_const, last_is_volatile, last_is_restrict, last_is_pointer, base_mode) + result;
        if (last_is_pointer && (is_pointer || type.handle_type->reference_type != halide_handle_cplusplus_type::NotReference)) {
            result = one_qualifier_set(last_is_const, last_is_volatile, last_is_restrict, false, base_mode) + result;
        }

        last_is_pointer = is_pointer;
        if (!is_pointer) {
            finished = true;
        }
    }

    void final() {
        if (!finished) {
            handle_modifier(0);
        }
        if (last_is_pointer) {
            result = one_qualifier_set(false, false, false, last_is_pointer, base_mode) + result;
        }

        if (type.handle_type->reference_type == halide_handle_cplusplus_type::LValueReference) {
            result = "A" + base_mode + result;  // Or is it "R"?
        } else if (type.handle_type->reference_type == halide_handle_cplusplus_type::RValueReference) {
            result = "$$Q" + base_mode + result;
        }
    }

    const std::string &get_result() const {
        return result;
    }
};

std::string mangle_indirection_and_cvr_quals(const Type &type, const Target &target) {
    QualsState state(type, (target.bits == 64) ? "E" : "");
    for (uint8_t modifier : type.handle_type->cpp_type_modifiers) {
        state.handle_modifier(modifier);
    }
    state.final();

    return state.get_result();
}

MangledNamePart mangle_inner_name(const Type &type, const Target &target, PreviousDeclarations &prev_decls) {
    MangledNamePart result("");

    std::string quals = mangle_indirection_and_cvr_quals(type, target);
    if (type.handle_type->inner_name.cpp_type_type == halide_cplusplus_type_name::Simple) {
        return quals + simple_type_to_mangle_char(type.handle_type->inner_name.name, target);
    } else {
        std::string code;
        if (type.handle_type->inner_name.cpp_type_type == halide_cplusplus_type_name::Struct) {
            code = "U";
        } else if (type.handle_type->inner_name.cpp_type_type == halide_cplusplus_type_name::Class) {
            code = "V";
        } else if (type.handle_type->inner_name.cpp_type_type == halide_cplusplus_type_name::Union) {
            code = "T";
        } else if (type.handle_type->inner_name.cpp_type_type == halide_cplusplus_type_name::Enum) {
            code = "W4";
        }
        result.full_name = quals + code + type.handle_type->inner_name.name + "@";
        result.with_substitutions = quals + code + prev_decls.check_and_enter_name(type.handle_type->inner_name.name);

        for (size_t i = type.handle_type->enclosing_types.size(); i > 0; i--) {
            result.full_name += type.handle_type->enclosing_types[i - 1].name + "@";
            result.with_substitutions += prev_decls.check_and_enter_name(type.handle_type->enclosing_types[i - 1].name);
        }

        for (size_t i = type.handle_type->namespaces.size(); i > 0; i--) {
            result.full_name += type.handle_type->namespaces[i - 1] + "@";
            result.with_substitutions += prev_decls.check_and_enter_name(type.handle_type->namespaces[i - 1]);
        }

        result.full_name += "@";
        result.with_substitutions += "@";

        return result;
    }
}

MangledNamePart mangle_type(const Type &type, const Target &target, PreviousDeclarations &prev_decls) {
    if (type.is_int()) {
        switch (type.bits()) {
        case 8:
            return "C";
        case 16:
            return "F";
        case 32:
            return "H";
        case 64:
            return "_J";
        }
        internal_error << "Unexpected integer size: " << type.bits() << ".\n";
        return "";
    } else if (type.is_uint()) {
        switch (type.bits()) {
        case 1:
            return "_N";
        case 8:
            return "E";
        case 16:
            return "G";
        case 32:
            return "I";
        case 64:
            return "_K";
        }
        internal_error << "Unexpected unsigned integer size: " << type.bits() << "\n";
        return "";
    } else if (type.is_float()) {
        if (type.bits() == 32) {
            return "M";
        } else if (type.bits() == 64) {
            return "N";
        }
        internal_error << "Unexpected floating-point type size: " << type.bits() << ".\n";
        return "";
    } else if (type.is_handle()) {
        return mangle_inner_name((type.handle_type != nullptr) ? type : non_null_void_star_type(),
                                 target, prev_decls);
    }
    internal_error << "Unexpected kind of type. Code: " << type.code() << "\n";
    return "";
}

std::string cplusplus_function_mangled_name(const std::string &name, const std::vector<std::string> &namespaces,
                                            Type return_type, const std::vector<ExternFuncArgument> &args,
                                            const Target &target) {
    std::string result("\1?");

    PreviousDeclarations prev_decls;
    result += prev_decls.check_and_enter_name(name);

    for (size_t i = namespaces.size(); i > 0; i--) {
        result += prev_decls.check_and_enter_name(namespaces[i - 1]);
    }
    result += "@";

    result += "YA";

    result += prev_decls.check_and_enter_type(mangle_type(return_type, target, prev_decls));

    if (args.empty()) {
        result += "X";
    } else {
        for (const auto &arg : args) {
            result += prev_decls.check_and_enter_type(mangle_type(arg.is_expr() ? arg.expr.type() : type_of<struct halide_buffer_t *>(), target, prev_decls));
        }
        // I think ending in a 'Z' only happens for nested function types, which never
        // occurs with Halide, but putting it in anyway per.
        // http://www.agner.org/optimize/calling_conventions.pdf
        if (result.back() != 'Z') {
            result += "@";
        }
    }
    result += "Z";

    return result;
}

}  // namespace WindowsMangling

namespace ItaniumABIMangling {

std::string itanium_mangle_id(const std::string &id) {
    std::ostringstream oss;
    oss << id.size() << id;
    return oss.str();
}

std::string simple_type_to_mangle_char(const std::string &type_name, const Target &target) {
    if (type_name == "void") {
        return "v";
    } else if (type_name == "bool") {
        return "b";
    } else if (type_name == "char") {
        return "c";
    }
    if (type_name == "int8_t") {
        return "a";
    } else if (type_name == "uint8_t") {
        return "h";
    } else if (type_name == "int16_t") {
        return "s";
    } else if (type_name == "uint16_t") {
        return "t";
    } else if (type_name == "int32_t") {
        return "i";
    } else if (type_name == "uint32_t") {
        return "j";
    } else if (type_name == "int64_t") {
        if (target.os == Target::OSX ||
            target.os == Target::IOS ||
            target.bits == 32) {
            return "x";
        } else {
            return "l";
        }
    } else if (type_name == "uint64_t") {
        if (target.os == Target::OSX ||
            target.os == Target::IOS ||
            target.bits == 32) {
            return "y";
        } else {
            return "m";
        }
    } else if (type_name == "float") {
        return "f";
    } else if (type_name == "double") {
        return "d";
    }
    user_error << "Unknown type name: " << type_name << "\n";
    return "";
}

struct Quals {
    std::string modifiers;
    std::string indirections;
};

struct PrevPrefixes {
    std::map<std::string, int32_t> prev_seen;

    bool check_and_enter(const std::string &prefix, std::string &substitute) {
        auto place = prev_seen.insert({prefix, prev_seen.size()});
        if (place.first->second == 0) {
            substitute = "S_";
        } else {
            // Convert to base 36, using digits and upper case letters for each digit.
            std::string seq_id;
            int32_t to_encode = place.first->second - 1;
            do {
                int least_sig_digit = to_encode % 36;
                if (least_sig_digit < 10) {
                    seq_id = std::string(1, (char)('0' + least_sig_digit)) + seq_id;
                } else {
                    seq_id = (char)('A' + (least_sig_digit - 10)) + seq_id;
                }
                to_encode /= 36;
            } while (to_encode > 0);
            substitute = "S" + seq_id + "_";
        }
        return !place.second;
    }

    bool extend_name_part(MangledNamePart &name_part, const std::string &mangled) {
        std::string substitute;
        bool found = check_and_enter(name_part.with_substitutions + mangled, substitute);
        if (found) {
            name_part.full_name = substitute;
        } else {
            name_part.full_name = name_part.full_name + mangled;
        }
        name_part.with_substitutions = substitute;
        return found;
    }

    bool prepend_name_part(const std::string &mangled, MangledNamePart &name_part) {
        std::string substitute;
        bool found = check_and_enter(mangled + name_part.with_substitutions, substitute);
        if (found) {
            name_part.full_name = substitute;
        } else {
            name_part.full_name = mangled + name_part.full_name;
        }
        name_part.with_substitutions = substitute;
        return found;
    }
};

MangledNamePart apply_indirection_and_cvr_quals(const Type &type, MangledNamePart &name_part,
                                                PrevPrefixes &prevs) {
    for (uint8_t modifier : type.handle_type->cpp_type_modifiers) {
        // Qualifiers on a value type are simply not encoded.
        // E.g. "int f(const int)" mangles the same as "int f(int)".
        if (!(modifier & halide_handle_cplusplus_type::Pointer) &&
            type.handle_type->reference_type == halide_handle_cplusplus_type::NotReference) {
            break;
        }

        std::string quals;

        if (modifier & halide_handle_cplusplus_type::Restrict) {
            quals += "r";
        }
        if (modifier & halide_handle_cplusplus_type::Volatile) {
            quals += "V";
        }
        if (modifier & halide_handle_cplusplus_type::Const) {
            quals += "K";
        }

        if (!quals.empty()) {
            prevs.prepend_name_part(quals, name_part);
        }

        if (modifier & halide_handle_cplusplus_type::Pointer) {
            prevs.prepend_name_part("P", name_part);
        } else {
            break;
        }
    }

    if (type.handle_type->reference_type == halide_handle_cplusplus_type::LValueReference) {
        prevs.prepend_name_part("R", name_part);
    } else if (type.handle_type->reference_type == halide_handle_cplusplus_type::RValueReference) {
        prevs.prepend_name_part("O", name_part);
    }

    return name_part;
}

MangledNamePart mangle_qualified_name(const std::string &name, const std::vector<std::string> &namespaces,
                                      const std::vector<halide_cplusplus_type_name> &enclosing_types,
                                      bool can_substitute, PrevPrefixes &prevs) {
    MangledNamePart result;

    // Nested names start with N and then have the enclosing scope names.
    bool is_directly_in_std = enclosing_types.empty() && (namespaces.size() == 1 && namespaces[0] == "std");
    bool not_simple = !is_directly_in_std && (!namespaces.empty() || !enclosing_types.empty());
    std::string substitute;
    if (is_directly_in_std) {
        // TODO: more cases here.
        if (name == "allocator") {
            return "Sa";
        } else if (name == "string") {  // Not correct, but it does the right thing
            return "Ss";
        }
        result.full_name += "St";
        result.with_substitutions += "St";
    } else if (not_simple) {
        for (const auto &ns : namespaces) {
            if (ns == "std") {
                result.full_name += "St";
                result.with_substitutions += "St";
            } else {
                prevs.extend_name_part(result, itanium_mangle_id(ns));
            }
        }
        for (const auto &et : enclosing_types) {
            prevs.extend_name_part(result, itanium_mangle_id(et.name));
        }
    }

    std::string mangled = itanium_mangle_id(name);
    bool substituted = false;
    if (can_substitute) {
        substituted = prevs.extend_name_part(result, mangled);
    } else {
        result.full_name += mangled;
        result.with_substitutions += mangled;
    }
    if (not_simple && !substituted) {
        result.full_name = "N" + result.full_name + "E";
    }

    return result;
}

std::string mangle_inner_name(const Type &type, const Target &target, PrevPrefixes &prevs) {
    if (type.handle_type->inner_name.cpp_type_type == halide_cplusplus_type_name::Simple) {
        MangledNamePart result = simple_type_to_mangle_char(type.handle_type->inner_name.name, target);
        return apply_indirection_and_cvr_quals(type, result, prevs).full_name;
    } else {
        MangledNamePart mangled = mangle_qualified_name(type.handle_type->inner_name.name, type.handle_type->namespaces,
                                                        type.handle_type->enclosing_types, true, prevs);
        return apply_indirection_and_cvr_quals(type, mangled, prevs).full_name;
    }
}

std::string mangle_type(const Type &type, const Target &target, PrevPrefixes &prevs) {
    if (type.is_int()) {
        switch (type.bits()) {
        case 8:
            return "a";
        case 16:
            return "s";
        case 32:
            if (target.arch == Target::Hexagon) {
                return "l";
            } else {
                return "i";
            }
        case 64:
            if (target.os == Target::OSX ||
                target.os == Target::IOS ||
                target.bits == 32) {
                return "x";
            } else {
                return "l";
            }
        }
        internal_error << "Unexpected integer size: " << type.bits() << ".\n";
        return "";
    } else if (type.is_uint()) {
        switch (type.bits()) {
        case 1:
            return "b";
        case 8:
            return "h";
        case 16:
            return "t";
        case 32:
            if (target.arch == Target::Hexagon) {
                return "m";
            } else {
                return "j";
            }
        case 64:
            if (target.os == Target::OSX ||
                target.os == Target::IOS ||
                target.bits == 32) {
                return "y";
            } else {
                return "m";
            }
        }
        internal_error << "Unexpected unsigned integer size: " << type.bits() << "\n";
        return "";
    } else if (type.is_float()) {
        if (type.bits() == 32) {
            return "f";
        } else if (type.bits() == 64) {
            return "d";
        }
        internal_error << "Unexpected floating-point type size: " << type.bits() << ".\n";
        return "";
    } else if (type.is_handle()) {
        return mangle_inner_name((type.handle_type != nullptr) ? type : non_null_void_star_type(),
                                 target, prevs);
    }
    internal_error << "Unexpected kind of type. Code: " << type.code() << "\n";
    return "";
}

std::string cplusplus_function_mangled_name(const std::string &name, const std::vector<std::string> &namespaces,
                                            Type return_type, const std::vector<ExternFuncArgument> &args,
                                            const Target &target) {
    std::string result("_Z");

    PrevPrefixes prevs;
    result += mangle_qualified_name(name, namespaces, {}, false, prevs).full_name;

    if (args.empty()) {
        result += "v";
    }

    for (const auto &arg : args) {
        result += mangle_type(arg.is_expr() ? arg.expr.type() : type_of<struct halide_buffer_t *>(), target, prevs);
    }

    return result;
}

}  // namespace ItaniumABIMangling

std::string cplusplus_function_mangled_name(const std::string &name, const std::vector<std::string> &namespaces,
                                            Type return_type, const std::vector<ExternFuncArgument> &args,
                                            const Target &target) {
    if (target.os == Target::Windows) {
        return WindowsMangling::cplusplus_function_mangled_name(name, namespaces, return_type, args, target);
    } else {
        return ItaniumABIMangling::cplusplus_function_mangled_name(name, namespaces, return_type, args, target);
    }
}

// All code below is for tests.

namespace {

constexpr int kTestTargetCount = 8;

struct MangleResult {
    const char *expected;
    const char *label;
};

MangleResult ItaniumABIMangling_main[] = {
    {"_Z13test_functionv", "int32_t test_function(void)"},
    {"_ZN3foo13test_functionEv", "int32_t foo::test_function(void)"},
    {"_ZN3foo3bar13test_functionEv", "int32_t foo::bar::test_function(void)"},
    {"_ZN3foo3bar13test_functionEi", "int32_t foo::test_function(int32_t)"},
    {"_ZN3foo3bar13test_functionEiP15halide_buffer_t", "int32_t foo::test_function(int32_t, struct halide_buffer_t *)"},
    {"_ZN14test_namespace14test_namespace13test_functionENS0_15enclosing_class11test_structE",
     "test_namespace::test_namespace::test_function(test_namespace::test_namespace::enclosing_class::test_struct)"},
    {"_ZN3foo3bar13test_functionEiP15halide_buffer_tS2_", "foo::bar::test_function(int, halide_buffer_t*, halide_buffer_t*)"},
    {"_ZN14test_namespace14test_namespace13test_functionEPNS_11test_structEPKS1_", "test_namespace::test_namespace::test_function(test_namespace::test_struct*, test_namespace::test_struct const*)"},
    {"_ZN14test_namespace14test_namespace13test_functionENS0_15enclosing_class11test_structES2_",
     "test_namespace::test_namespace::test_function(test_namespace::test_namespace::enclosing_class::test_struct, test_namespace::test_namespace::enclosing_class::test_struct)"},
    {"_ZSt13test_functionv", "std::test_function()"},
    {"_ZNSt3foo13test_functionEv", "std::foo::test_function()"},
    {"_ZSt13test_functionNSt15enclosing_class11test_structE", "std::test_function(std::enclosing_class::test_struct)"},
    {"_ZN14test_namespace14test_namespace13test_functionEPNS_10test_classE", "test_namespace::test_namespace::test_function(test_namespace::test_class*)"},
    {"_ZN14test_namespace14test_namespace13test_functionEPNS_10test_unionE", "test_namespace::test_namespace::test_function(test_namespace::test_union*)"},
    {"_ZN14test_namespace14test_namespace13test_functionEPNS_9test_enumE", "test_namespace::test_namespace::test_function(test_namespace::test_enum*)"},
};

MangleResult win32_expecteds[] = {
    {"\001?test_function@@YAHXZ", "int32_t test_function(void)"},
    {"\001?test_function@foo@@YAHXZ", "int32_t foo::test_function(void)"},
    {"\001?test_function@bar@foo@@YAHXZ", "int32_t foo::bar::test_function(void)"},
    {"\001?test_function@bar@foo@@YAHH@Z", "int32_t foo::test_function(int32_t)"},
    {"\001?test_function@bar@foo@@YAHHPAUhalide_buffer_t@@@Z", "int32_t foo::test_function(int32_t, struct halide_buffer_t *)"},
    {"\001?test_function@test_namespace@1@YAHUtest_struct@enclosing_class@11@@Z",
     "test_namespace::test_namespace::test_function(test_namespace::test_namespace::enclosing_class::test_struct)"},
    {"\001?test_function@bar@foo@@YAHHPAUhalide_buffer_t@@0@Z", "foo::bar::test_function(int, halide_buffer_t*, halide_buffer_t*)"},
    {"\001?test_function@test_namespace@1@YAHPAUtest_struct@1@PBU21@@Z", "test_namespace::test_namespace::test_function(test_namespace::test_struct*, test_namespace::test_struct const*)"},
    {"\001?test_function@test_namespace@1@YAHUtest_struct@enclosing_class@11@0@Z",
     "test_namespace::test_namespace::test_function(test_namespace::test_namespace::enclosing_class::test_struct, test_namespace::test_namespace::enclosing_class::test_struct)"},
    {"\001?test_function@std@@YAHXZ", "std::test_function()"},
    {"\001?test_function@foo@std@@YAHXZ", "std::foo::test_function()"},
    {"\001?test_function@std@@YAHUtest_struct@enclosing_class@1@@Z", "std::test_function(std::enclosing_class::test_struct)"},
    {"\001?test_function@test_namespace@1@YAHPAVtest_class@1@@Z", "test_namespace::test_namespace::test_function(test_namespace::test_class*)"},
    {"\001?test_function@test_namespace@1@YAHPATtest_union@1@@Z", "test_namespace::test_namespace::test_function(test_namespace::test_union*)"},
    {"\001?test_function@test_namespace@1@YAHPAVtest_enum@1@@Z", "test_namespace::test_namespace::test_function(test_namespace::test_enum*)"},
};

MangleResult win64_expecteds[] = {
    {"\001?test_function@@YAHXZ", "int32_t test_function(void)"},
    {"\001?test_function@foo@@YAHXZ", "int32_t foo::test_function(void)"},
    {"\001?test_function@bar@foo@@YAHXZ", "int32_t foo::bar::test_function(void)"},
    {"\001?test_function@bar@foo@@YAHH@Z", "int32_t foo::test_function(int32_t)"},
    {"\001?test_function@bar@foo@@YAHHPEAUhalide_buffer_t@@@Z", "int32_t foo::test_function(int32_t, struct halide_buffer_t *)"},
    {"\001?test_function@test_namespace@1@YAHUtest_struct@enclosing_class@11@@Z",
     "test_namespace::test_namespace::test_function(test_namespace::test_namespace::enclosing_class::test_struct)"},
    {"\001?test_function@bar@foo@@YAHHPEAUhalide_buffer_t@@0@Z", "foo::bar::test_function(int, halide_buffer_t*, halide_buffer_t*)"},
    {"\001?test_function@test_namespace@1@YAHPEAUtest_struct@1@PEBU21@@Z", "test_namespace::test_namespace::test_function(test_namespace::test_struct*, test_namespace::test_struct const*)"},
    {"\001?test_function@test_namespace@1@YAHUtest_struct@enclosing_class@11@0@Z",
     "test_namespace::test_namespace::test_function(test_namespace::test_namespace::enclosing_class::test_struct, test_namespace::test_namespace::enclosing_class::test_struct)"},
    {"\001?test_function@std@@YAHXZ", "std::test_function()"},
    {"\001?test_function@foo@std@@YAHXZ", "std::foo::test_function()"},
    {"\001?test_function@std@@YAHUtest_struct@enclosing_class@1@@Z", "std::test_function(std::enclosing_class::test_struct)"},
    {"\001?test_function@test_namespace@1@YAHPEAVtest_class@1@@Z", "test_namespace::test_namespace::test_function(test_namespace::test_class*)"},
    {"\001?test_function@test_namespace@1@YAHPEATtest_union@1@@Z", "test_namespace::test_namespace::test_function(test_namespace::test_union*)"},
    {"\001?test_function@test_namespace@1@YAHPEAVtest_enum@1@@Z", "test_namespace::test_namespace::test_function(test_namespace::test_enum*)"},
};

MangleResult all_types_by_target[kTestTargetCount] = {
    {"_Z13test_functionbahstijxyfd", "test_function(bool, signed char, unsigned char, short, unsigned short, int, unsigned int, long long, unsigned long long, float, double)"},
    {"_Z13test_functionbahstijlmfd", "test_function(bool, signed char, unsigned char, short, unsigned short, int, unsigned int, long, unsigned long, float, double)"},
    {"_Z13test_functionbahstijxyfd", "test_function(bool, signed char, unsigned char, short, unsigned short, int, unsigned int, long long, unsigned long long, float, double)"},
    {"_Z13test_functionbahstijxyfd", "test_function(bool, signed char, unsigned char, short, unsigned short, int, unsigned int, long, unsigned long, float, double)"},
    {"_Z13test_functionbahstijxyfd", "test_function(bool, signed char, unsigned char, short, unsigned short, int, unsigned int, long long, unsigned long long, float, double)"},
    {"_Z13test_functionbahstijxyfd", "test_function(bool, signed char, unsigned char, short, unsigned short, int, unsigned int, long, unsigned long, float, double)"},
    {"\001?test_function@@YAH_NCEFGHI_J_KMN@Z", "test_function(bool, signed char, unsigned char, short, unsigned short, int, unsigned int, long long, unsigned long long, float, double)"},
    {"\001?test_function@@YAH_NCEFGHI_J_KMN@Z", "test_function(bool, signed char, unsigned char, short, unsigned short, int, unsigned int, long long, unsigned long long, float, double)"},
};

const char *many_type_subs_itanium = "_Z13test_functionPN14test_namespace2s0EPNS_2s1EPNS_2s2EPNS_2s3EPNS_2s4EPNS_2s5EPNS_2s6EPNS_2s7EPNS_2s8EPNS_2s9EPNS_3s10EPNS_3s11EPNS_3s12EPNS_3s13EPNS_3s14EPNS_3s15EPNS_3s16EPNS_3s17EPNS_3s18EPNS_3s19EPNS_3s20EPNS_3s21EPNS_3s22EPNS_3s23EPNS_3s24EPNS_3s25EPNS_3s26EPNS_3s27EPNS_3s28EPNS_3s29EPNS_3s30EPNS_3s31EPNS_3s32EPNS_3s33EPNS_3s34EPNS_3s35EPNS_3s36EPNS_3s37EPNS_3s38EPNS_3s39EPNS_3s40EPNS_3s41EPNS_3s42EPNS_3s43EPNS_3s44EPNS_3s45EPNS_3s46EPNS_3s47EPNS_3s48EPNS_3s49EPNS_3s50EPNS_3s51EPNS_3s52EPNS_3s53EPNS_3s54EPNS_3s55EPNS_3s56EPNS_3s57EPNS_3s58EPNS_3s59EPNS_3s60EPNS_3s61EPNS_3s62EPNS_3s63EPNS_3s64EPNS_3s65EPNS_3s66EPNS_3s67EPNS_3s68EPNS_3s69EPNS_3s70EPNS_3s71EPNS_3s72EPNS_3s73EPNS_3s74EPNS_3s75EPNS_3s76EPNS_3s77EPNS_3s78EPNS_3s79EPNS_3s80EPNS_3s81EPNS_3s82EPNS_3s83EPNS_3s84EPNS_3s85EPNS_3s86EPNS_3s87EPNS_3s88EPNS_3s89EPNS_3s90EPNS_3s91EPNS_3s92EPNS_3s93EPNS_3s94EPNS_3s95EPNS_3s96EPNS_3s97EPNS_3s98EPNS_3s99ES1_S3_S5_S7_S9_SB_SD_SF_SH_SJ_SL_SN_SP_SR_ST_SV_SX_SZ_S11_S13_S15_S17_S19_S1B_S1D_S1F_S1H_S1J_S1L_S1N_S1P_S1R_S1T_S1V_S1X_S1Z_S21_S23_S25_S27_S29_S2B_S2D_S2F_S2H_S2J_S2L_S2N_S2P_S2R_S2T_S2V_S2X_S2Z_S31_S33_S35_S37_S39_S3B_S3D_S3F_S3H_S3J_S3L_S3N_S3P_S3R_S3T_S3V_S3X_S3Z_S41_S43_S45_S47_S49_S4B_S4D_S4F_S4H_S4J_S4L_S4N_S4P_S4R_S4T_S4V_S4X_S4Z_S51_S53_S55_S57_S59_S5B_S5D_S5F_S5H_S5J_";

const char *many_type_subs_win32 = "\001?test_function@@YAHPAUs0@test_namespace@@PAUs1@2@PAUs2@2@PAUs3@2@PAUs4@2@PAUs5@2@PAUs6@2@PAUs7@2@PAUs8@2@PAUs9@2@PAUs10@2@PAUs11@2@PAUs12@2@PAUs13@2@PAUs14@2@PAUs15@2@PAUs16@2@PAUs17@2@PAUs18@2@PAUs19@2@PAUs20@2@PAUs21@2@PAUs22@2@PAUs23@2@PAUs24@2@PAUs25@2@PAUs26@2@PAUs27@2@PAUs28@2@PAUs29@2@PAUs30@2@PAUs31@2@PAUs32@2@PAUs33@2@PAUs34@2@PAUs35@2@PAUs36@2@PAUs37@2@PAUs38@2@PAUs39@2@PAUs40@2@PAUs41@2@PAUs42@2@PAUs43@2@PAUs44@2@PAUs45@2@PAUs46@2@PAUs47@2@PAUs48@2@PAUs49@2@PAUs50@2@PAUs51@2@PAUs52@2@PAUs53@2@PAUs54@2@PAUs55@2@PAUs56@2@PAUs57@2@PAUs58@2@PAUs59@2@PAUs60@2@PAUs61@2@PAUs62@2@PAUs63@2@PAUs64@2@PAUs65@2@PAUs66@2@PAUs67@2@PAUs68@2@PAUs69@2@PAUs70@2@PAUs71@2@PAUs72@2@PAUs73@2@PAUs74@2@PAUs75@2@PAUs76@2@PAUs77@2@PAUs78@2@PAUs79@2@PAUs80@2@PAUs81@2@PAUs82@2@PAUs83@2@PAUs84@2@PAUs85@2@PAUs86@2@PAUs87@2@PAUs88@2@PAUs89@2@PAUs90@2@PAUs91@2@PAUs92@2@PAUs93@2@PAUs94@2@PAUs95@2@PAUs96@2@PAUs97@2@PAUs98@2@PAUs99@2@0123456789PAUs10@2@PAUs11@2@PAUs12@2@PAUs13@2@PAUs14@2@PAUs15@2@PAUs16@2@PAUs17@2@PAUs18@2@PAUs19@2@PAUs20@2@PAUs21@2@PAUs22@2@PAUs23@2@PAUs24@2@PAUs25@2@PAUs26@2@PAUs27@2@PAUs28@2@PAUs29@2@PAUs30@2@PAUs31@2@PAUs32@2@PAUs33@2@PAUs34@2@PAUs35@2@PAUs36@2@PAUs37@2@PAUs38@2@PAUs39@2@PAUs40@2@PAUs41@2@PAUs42@2@PAUs43@2@PAUs44@2@PAUs45@2@PAUs46@2@PAUs47@2@PAUs48@2@PAUs49@2@PAUs50@2@PAUs51@2@PAUs52@2@PAUs53@2@PAUs54@2@PAUs55@2@PAUs56@2@PAUs57@2@PAUs58@2@PAUs59@2@PAUs60@2@PAUs61@2@PAUs62@2@PAUs63@2@PAUs64@2@PAUs65@2@PAUs66@2@PAUs67@2@PAUs68@2@PAUs69@2@PAUs70@2@PAUs71@2@PAUs72@2@PAUs73@2@PAUs74@2@PAUs75@2@PAUs76@2@PAUs77@2@PAUs78@2@PAUs79@2@PAUs80@2@PAUs81@2@PAUs82@2@PAUs83@2@PAUs84@2@PAUs85@2@PAUs86@2@PAUs87@2@PAUs88@2@PAUs89@2@PAUs90@2@PAUs91@2@PAUs92@2@PAUs93@2@PAUs94@2@PAUs95@2@PAUs96@2@PAUs97@2@PAUs98@2@PAUs99@2@@Z";

const char *many_type_subs_win64 = "\001?test_function@@YAHPEAUs0@test_namespace@@PEAUs1@2@PEAUs2@2@PEAUs3@2@PEAUs4@2@PEAUs5@2@PEAUs6@2@PEAUs7@2@PEAUs8@2@PEAUs9@2@PEAUs10@2@PEAUs11@2@PEAUs12@2@PEAUs13@2@PEAUs14@2@PEAUs15@2@PEAUs16@2@PEAUs17@2@PEAUs18@2@PEAUs19@2@PEAUs20@2@PEAUs21@2@PEAUs22@2@PEAUs23@2@PEAUs24@2@PEAUs25@2@PEAUs26@2@PEAUs27@2@PEAUs28@2@PEAUs29@2@PEAUs30@2@PEAUs31@2@PEAUs32@2@PEAUs33@2@PEAUs34@2@PEAUs35@2@PEAUs36@2@PEAUs37@2@PEAUs38@2@PEAUs39@2@PEAUs40@2@PEAUs41@2@PEAUs42@2@PEAUs43@2@PEAUs44@2@PEAUs45@2@PEAUs46@2@PEAUs47@2@PEAUs48@2@PEAUs49@2@PEAUs50@2@PEAUs51@2@PEAUs52@2@PEAUs53@2@PEAUs54@2@PEAUs55@2@PEAUs56@2@PEAUs57@2@PEAUs58@2@PEAUs59@2@PEAUs60@2@PEAUs61@2@PEAUs62@2@PEAUs63@2@PEAUs64@2@PEAUs65@2@PEAUs66@2@PEAUs67@2@PEAUs68@2@PEAUs69@2@PEAUs70@2@PEAUs71@2@PEAUs72@2@PEAUs73@2@PEAUs74@2@PEAUs75@2@PEAUs76@2@PEAUs77@2@PEAUs78@2@PEAUs79@2@PEAUs80@2@PEAUs81@2@PEAUs82@2@PEAUs83@2@PEAUs84@2@PEAUs85@2@PEAUs86@2@PEAUs87@2@PEAUs88@2@PEAUs89@2@PEAUs90@2@PEAUs91@2@PEAUs92@2@PEAUs93@2@PEAUs94@2@PEAUs95@2@PEAUs96@2@PEAUs97@2@PEAUs98@2@PEAUs99@2@0123456789PEAUs10@2@PEAUs11@2@PEAUs12@2@PEAUs13@2@PEAUs14@2@PEAUs15@2@PEAUs16@2@PEAUs17@2@PEAUs18@2@PEAUs19@2@PEAUs20@2@PEAUs21@2@PEAUs22@2@PEAUs23@2@PEAUs24@2@PEAUs25@2@PEAUs26@2@PEAUs27@2@PEAUs28@2@PEAUs29@2@PEAUs30@2@PEAUs31@2@PEAUs32@2@PEAUs33@2@PEAUs34@2@PEAUs35@2@PEAUs36@2@PEAUs37@2@PEAUs38@2@PEAUs39@2@PEAUs40@2@PEAUs41@2@PEAUs42@2@PEAUs43@2@PEAUs44@2@PEAUs45@2@PEAUs46@2@PEAUs47@2@PEAUs48@2@PEAUs49@2@PEAUs50@2@PEAUs51@2@PEAUs52@2@PEAUs53@2@PEAUs54@2@PEAUs55@2@PEAUs56@2@PEAUs57@2@PEAUs58@2@PEAUs59@2@PEAUs60@2@PEAUs61@2@PEAUs62@2@PEAUs63@2@PEAUs64@2@PEAUs65@2@PEAUs66@2@PEAUs67@2@PEAUs68@2@PEAUs69@2@PEAUs70@2@PEAUs71@2@PEAUs72@2@PEAUs73@2@PEAUs74@2@PEAUs75@2@PEAUs76@2@PEAUs77@2@PEAUs78@2@PEAUs79@2@PEAUs80@2@PEAUs81@2@PEAUs82@2@PEAUs83@2@PEAUs84@2@PEAUs85@2@PEAUs86@2@PEAUs87@2@PEAUs88@2@PEAUs89@2@PEAUs90@2@PEAUs91@2@PEAUs92@2@PEAUs93@2@PEAUs94@2@PEAUs95@2@PEAUs96@2@PEAUs97@2@PEAUs98@2@PEAUs99@2@@Z";

MangleResult many_type_subs[kTestTargetCount] = {
    {many_type_subs_itanium, "The expanded prototype is very long."},
    {many_type_subs_itanium, "No really, too large to put here."},
    {many_type_subs_itanium, "wc -l says 4394 characters."},
    {many_type_subs_itanium, "Feel free to run c++filt if you want to..."},
    {many_type_subs_itanium, "longity long long"},
    {many_type_subs_itanium, "It's been a long, long, long..."},
    {many_type_subs_win32, "Not gonna do it."},
    {many_type_subs_win64, "Wouldn't be prudent."}};

const char *many_name_subs_itanium = "_Z13test_functionPN15test_namespace01sEPN15test_namespace11sEPN15test_namespace21sEPN15test_namespace31sEPN15test_namespace41sEPN15test_namespace51sEPN15test_namespace61sEPN15test_namespace71sEPN15test_namespace81sEPN15test_namespace91sEPN16test_namespace101sEPN16test_namespace111sEPN16test_namespace121sEPN16test_namespace131sEPN16test_namespace141sEPN16test_namespace151sEPN16test_namespace161sEPN16test_namespace171sEPN16test_namespace181sEPN16test_namespace191sEPN16test_namespace201sEPN16test_namespace211sEPN16test_namespace221sEPN16test_namespace231sEPN16test_namespace241sES1_S4_S7_SA_SD_SG_SJ_SM_SP_SS_SV_SY_S11_S14_S17_S1A_S1D_S1G_S1J_S1M_S1P_S1S_S1V_S1Y_S21_";

const char *many_name_subs_win32 = "\001?test_function@@YAHPAUs@test_namespace0@@PAU1test_namespace1@@PAU1test_namespace2@@PAU1test_namespace3@@PAU1test_namespace4@@PAU1test_namespace5@@PAU1test_namespace6@@PAU1test_namespace7@@PAU1test_namespace8@@PAU1test_namespace9@@PAU1test_namespace10@@PAU1test_namespace11@@PAU1test_namespace12@@PAU1test_namespace13@@PAU1test_namespace14@@PAU1test_namespace15@@PAU1test_namespace16@@PAU1test_namespace17@@PAU1test_namespace18@@PAU1test_namespace19@@PAU1test_namespace20@@PAU1test_namespace21@@PAU1test_namespace22@@PAU1test_namespace23@@PAU1test_namespace24@@0123456789PAU1test_namespace10@@PAU1test_namespace11@@PAU1test_namespace12@@PAU1test_namespace13@@PAU1test_namespace14@@PAU1test_namespace15@@PAU1test_namespace16@@PAU1test_namespace17@@PAU1test_namespace18@@PAU1test_namespace19@@PAU1test_namespace20@@PAU1test_namespace21@@PAU1test_namespace22@@PAU1test_namespace23@@PAU1test_namespace24@@@Z";

const char *many_name_subs_win64 = "\001?test_function@@YAHPEAUs@test_namespace0@@PEAU1test_namespace1@@PEAU1test_namespace2@@PEAU1test_namespace3@@PEAU1test_namespace4@@PEAU1test_namespace5@@PEAU1test_namespace6@@PEAU1test_namespace7@@PEAU1test_namespace8@@PEAU1test_namespace9@@PEAU1test_namespace10@@PEAU1test_namespace11@@PEAU1test_namespace12@@PEAU1test_namespace13@@PEAU1test_namespace14@@PEAU1test_namespace15@@PEAU1test_namespace16@@PEAU1test_namespace17@@PEAU1test_namespace18@@PEAU1test_namespace19@@PEAU1test_namespace20@@PEAU1test_namespace21@@PEAU1test_namespace22@@PEAU1test_namespace23@@PEAU1test_namespace24@@0123456789PEAU1test_namespace10@@PEAU1test_namespace11@@PEAU1test_namespace12@@PEAU1test_namespace13@@PEAU1test_namespace14@@PEAU1test_namespace15@@PEAU1test_namespace16@@PEAU1test_namespace17@@PEAU1test_namespace18@@PEAU1test_namespace19@@PEAU1test_namespace20@@PEAU1test_namespace21@@PEAU1test_namespace22@@PEAU1test_namespace23@@PEAU1test_namespace24@@@Z";

const char *many_name_subs_proto = "test_function(test_namespace0::s*, test_namespace1::s*, test_namespace2::s*, test_namespace3::s*, test_namespace4::s*, test_namespace5::s*, test_namespace6::s*, test_namespace7::s*, test_namespace8::s*, test_namespace9::s*, test_namespace10::s*, test_namespace11::s*, test_namespace12::s*, test_namespace13::s*, test_namespace14::s*, test_namespace15::s*, test_namespace16::s*, test_namespace17::s*, test_namespace18::s*, test_namespace19::s*, test_namespace20::s*, test_namespace21::s*, test_namespace22::s*, test_namespace23::s*, test_namespace24::s*, test_namespace0::s*, test_namespace1::s*, test_namespace2::s*, test_namespace3::s*, test_namespace4::s*, test_namespace5::s*, test_namespace6::s*, test_namespace7::s*, test_namespace8::s*, test_namespace9::s*, test_namespace10::s*, test_namespace11::s*, test_namespace12::s*, test_namespace13::s*, test_namespace14::s*, test_namespace15::s*, test_namespace16::s*, test_namespace17::s*, test_namespace18::s*, test_namespace19::s*, test_namespace20::s*, test_namespace21::s*, test_namespace22::s*, test_namespace23::s*, test_namespace24::s*)";

MangleResult many_name_subs[kTestTargetCount] = {
    {many_name_subs_itanium, many_name_subs_proto},
    {many_name_subs_itanium, many_name_subs_proto},
    {many_name_subs_itanium, many_name_subs_proto},
    {many_name_subs_itanium, many_name_subs_proto},
    {many_name_subs_itanium, many_name_subs_proto},
    {many_name_subs_itanium, many_name_subs_proto},
    {many_name_subs_win32, many_name_subs_proto},
    {many_name_subs_win64, many_name_subs_proto}};

MangleResult stacked_indirections[kTestTargetCount] = {
    {"_Z13test_functionPKiPKS0_PKS2_PKS4_PKS6_PKS8_PKSA_PKSC_", ""},
    {"_Z13test_functionPKiPKS0_PKS2_PKS4_PKS6_PKS8_PKSA_PKSC_", ""},
    {"_Z13test_functionPKiPKS0_PKS2_PKS4_PKS6_PKS8_PKSA_PKSC_", ""},
    {"_Z13test_functionPKiPKS0_PKS2_PKS4_PKS6_PKS8_PKSA_PKSC_", ""},
    {"_Z13test_functionPKiPKS0_PKS2_PKS4_PKS6_PKS8_PKSA_PKSC_", ""},
    {"_Z13test_functionPKiPKS0_PKS2_PKS4_PKS6_PKS8_PKSA_PKSC_", ""},
    {"\001?test_function@@YAHPBHPBQBHPBQBQBHPBQBQBQBHPBQBQBQBQBHPBQBQBQBQBQBHPBQBQBQBQBQBQBHPBQBQBQBQBQBQBQBH@Z", ""},
    {"\001?test_function@@YAHPEBHPEBQEBHPEBQEBQEBHPEBQEBQEBQEBHPEBQEBQEBQEBQEBHPEBQEBQEBQEBQEBQEBHPEBQEBQEBQEBQEBQEBQEBHPEBQEBQEBQEBQEBQEBQEBQEBH@Z", ""}};

MangleResult all_mods_itanium[] = {
    {"_Z13test_function1sRS_OS_", "test_function(s, s&, s&&)"},
    {"_Z13test_function1sRKS_OS0_", "test_function(s, s const&, s const&&)"},
    {"_Z13test_function1sRVS_OS0_", "test_function(s, s volatile&, s volatile&&)"},
    {"_Z13test_function1sRVKS_OS0_", "test_function(s, s const volatile&, s const volatile&&)"},
    {"_Z13test_function1sRrS_OS0_", "test_function(s, s restrict&, s restrict&&)"},
    {"_Z13test_function1sRrKS_OS0_", "test_function(s, s const restrict&, s const restrict&&)"},
    {"_Z13test_function1sRrVS_OS0_", "test_function(s, s volatile restrict&, s volatile restrict&&)"},
    {"_Z13test_function1sRrVKS_OS0_", "test_function(s, s const volatile restrict&, s const volatile restrict&&)"},
    {"_Z13test_functionP1sRS0_OS0_", "test_function(s*, s*&, s*&&)"},
    {"_Z13test_functionPK1sRS1_OS1_", "test_function(s const*, s const*&, s const*&&)"},
    {"_Z13test_functionPV1sRS1_OS1_", "test_function(s volatile*, s volatile*&, s volatile*&&)"},
    {"_Z13test_functionPVK1sRS1_OS1_", "test_function(s const volatile*, s const volatile*&, s const volatile*&&)"},
    {"_Z13test_functionPr1sRS1_OS1_", "test_function(s restrict*, s restrict*&, s restrict*&&)"},
    {"_Z13test_functionPrK1sRS1_OS1_", "test_function(s const restrict*, s const restrict*&, s const restrict*&&)"},
    {"_Z13test_functionPrV1sRS1_OS1_", "test_function(s volatile restrict*, s volatile restrict*&, s volatile restrict*&&)"},
    {"_Z13test_functionPrVK1sRS1_OS1_", "test_function(s const volatile restrict*, s const volatile restrict*&, s const volatile restrict*&&)"}};

MangleResult all_mods_win32[] = {
    {"\001?test_function@@YAHUs@@AAU1@$$QAU1@@Z", "test_function(s, s&, s&&)"},
    {"\001?test_function@@YAHUs@@ABU1@$$QBU1@@Z", "test_function(s, s const&, s const&&)"},
    {"\001?test_function@@YAHUs@@ACU1@$$QCU1@@Z", "test_function(s, s volatile&, s volatile&&)"},
    {"\001?test_function@@YAHUs@@ADU1@$$QDU1@@Z", "test_function(s, s const volatile&, s const volatile&&)"},
    {"\001?test_function@@YAHUs@@AAU1@$$QAU1@@Z", "test_function(s, s restrict&, s restrict&&)"},
    {"\001?test_function@@YAHUs@@ABU1@$$QBU1@@Z", "test_function(s, s const restrict&, s const restrict&&)"},
    {"\001?test_function@@YAHUs@@ACU1@$$QCU1@@Z", "test_function(s, s volatile restrict&, s volatile restrict&&)"},
    {"\001?test_function@@YAHUs@@ADU1@$$QDU1@@Z", "test_function(s, s const volatile restrict&, s const volatile restrict&&)"},
    {"\001?test_function@@YAHPAUs@@AAPAU1@$$QAPAU1@@Z", "test_function(s*, s*&, s*&&)"},
    {"\001?test_function@@YAHPBUs@@AAPBU1@$$QAPBU1@@Z", "test_function(s const*, s const*&, s const*&&)"},
    {"\001?test_function@@YAHPCUs@@AAPCU1@$$QAPCU1@@Z", "test_function(s volatile*, s volatile*&, s volatile*&&)"},
    {"\001?test_function@@YAHPDUs@@AAPDU1@$$QAPDU1@@Z", "test_function(s const volatile*, s const volatile*&, s const volatile*&&)"},
    {"\001?test_function@@YAHPAUs@@AAPAU1@$$QAPAU1@@Z", "test_function(s restrict*, s restrict*&, s restrict*&&)"},
    {"\001?test_function@@YAHPBUs@@AAPBU1@$$QAPBU1@@Z", "test_function(s const restrict*, s const restrict*&, s const restrict*&&)"},
    {"\001?test_function@@YAHPCUs@@AAPCU1@$$QAPCU1@@Z", "test_function(s volatile restrict*, s volatile restrict*&, s volatile restrict*&&)"},
    {"\001?test_function@@YAHPDUs@@AAPDU1@$$QAPDU1@@Z", "test_function(s const volatile restrict*, s const volatile restrict*&, s const volatile restrict*&&)"}};

MangleResult all_mods_win64[] = {
    {"\001?test_function@@YAHUs@@AEAU1@$$QEAU1@@Z", "test_function(s, s&, s&&)"},
    {"\001?test_function@@YAHUs@@AEBU1@$$QEBU1@@Z", "test_function(s, s const&, s const&&)"},
    {"\001?test_function@@YAHUs@@AECU1@$$QECU1@@Z", "test_function(s, s volatile&, s volatile&&)"},
    {"\001?test_function@@YAHUs@@AEDU1@$$QEDU1@@Z", "test_function(s, s const volatile&, s const volatile&&)"},
    {"\001?test_function@@YAHUs@@AEAU1@$$QEAU1@@Z", "test_function(s, s restrict&, s restrict&&)"},
    {"\001?test_function@@YAHUs@@AEBU1@$$QEBU1@@Z", "test_function(s, s const restrict&, s const restrict&&)"},
    {"\001?test_function@@YAHUs@@AECU1@$$QECU1@@Z", "test_function(s, s volatile restrict&, s volatile restrict&&)"},
    {"\001?test_function@@YAHUs@@AEDU1@$$QEDU1@@Z", "test_function(s, s const volatile restrict&, s const volatile restrict&&)"},
    {"\001?test_function@@YAHPEAUs@@AEAPEAU1@$$QEAPEAU1@@Z", "test_function(s*, s*&, s*&&)"},
    {"\001?test_function@@YAHPEBUs@@AEAPEBU1@$$QEAPEBU1@@Z", "test_function(s const*, s const*&, s const*&&)"},
    {"\001?test_function@@YAHPECUs@@AEAPECU1@$$QEAPECU1@@Z", "test_function(s volatile*, s volatile*&, s volatile*&&)"},
    {"\001?test_function@@YAHPEDUs@@AEAPEDU1@$$QEAPEDU1@@Z", "test_function(s const volatile*, s const volatile*&, s const volatile*&&)"},
    {"\001?test_function@@YAHPEAUs@@AEAPEAU1@$$QEAPEAU1@@Z", "test_function(s restrict*, s restrict*&, s restrict*&&)"},
    {"\001?test_function@@YAHPEBUs@@AEAPEBU1@$$QEAPEBU1@@Z", "test_function(s const restrict*, s const restrict*&, s const restrict*&&)"},
    {"\001?test_function@@YAHPECUs@@AEAPECU1@$$QEAPECU1@@Z", "test_function(s volatile restrict*, s volatile restrict*&, s volatile restrict*&&)"},
    {"\001?test_function@@YAHPEDUs@@AEAPEDU1@$$QEAPEDU1@@Z", "test_function(s const volatile restrict*, s const volatile restrict*&, s const volatile restrict*&&)"},
};

MangleResult two_void_stars_itanium[] = {
    {"_Z13test_functionPvS_", "test_function(void *, void *)"},
};

MangleResult two_void_stars_win64[] = {
    {"\001?test_function@@YAHPEAX0@Z", "test_function(void *, void *)"},
};

MangleResult two_void_stars_win32[] = {
    {"\001?test_function@@YAHPAX0@Z", "test_function(void *, void *)"},
};

void check_result(const MangleResult *expecteds, size_t &expected_index,
                  const Target &target, const std::string &mangled_name) {
    internal_assert(mangled_name == expecteds[expected_index].expected) << "Mangling for " << expecteds[expected_index].label << " expected\n    " << expecteds[expected_index].expected << " got\n    " << mangled_name << "\nfor target " << target.to_string();
    expected_index++;
}

void main_tests(const MangleResult *expecteds, const Target &target) {
    size_t expecteds_index = 0;
    check_result(expecteds, expecteds_index, target,
                 cplusplus_function_mangled_name("test_function", {}, Int(32), {}, target));

    check_result(expecteds, expecteds_index, target,
                 cplusplus_function_mangled_name("test_function", {"foo"}, Int(32), {}, target));

    check_result(expecteds, expecteds_index, target,
                 cplusplus_function_mangled_name("test_function", {"foo", "bar"}, Int(32), {}, target));

    check_result(expecteds, expecteds_index, target,
                 cplusplus_function_mangled_name("test_function", {"foo", "bar"}, Int(32),
                                                 {ExternFuncArgument(42)}, target));

    check_result(expecteds, expecteds_index, target,
                 cplusplus_function_mangled_name("test_function", {"foo", "bar"}, Int(32),
                                                 {ExternFuncArgument(42), ExternFuncArgument(Buffer<>())}, target));

    halide_handle_cplusplus_type enclosed_type_info(halide_handle_cplusplus_type(
        halide_cplusplus_type_name(halide_cplusplus_type_name::Struct, "test_struct"),
        {"test_namespace", "test_namespace"},
        {halide_cplusplus_type_name(halide_cplusplus_type_name::Class,
                                    "enclosing_class")}));
    Type test_type(Handle(1, &enclosed_type_info));
    check_result(expecteds, expecteds_index, target,
                 cplusplus_function_mangled_name("test_function", {"test_namespace", "test_namespace"}, Int(32),
                                                 {ExternFuncArgument(make_zero(test_type))}, target));

    check_result(expecteds, expecteds_index, target,
                 cplusplus_function_mangled_name("test_function", {"foo", "bar"}, Int(32),
                                                 {ExternFuncArgument(42), ExternFuncArgument(Buffer<>()),
                                                  ExternFuncArgument(Buffer<>())},
                                                 target));

    halide_handle_cplusplus_type qual1(halide_handle_cplusplus_type(
        halide_cplusplus_type_name(halide_cplusplus_type_name::Struct, "test_struct"),
        {
            "test_namespace",
        },
        {}, {halide_handle_cplusplus_type::Pointer}));
    Type qual1_type(Handle(1, &qual1));
    halide_handle_cplusplus_type qual2(halide_handle_cplusplus_type(
        halide_cplusplus_type_name(halide_cplusplus_type_name::Struct, "test_struct"),
        {
            "test_namespace",
        },
        {}, {halide_handle_cplusplus_type::Pointer | halide_handle_cplusplus_type::Const}));
    Type qual2_type(Handle(1, &qual2));
    check_result(expecteds, expecteds_index, target,
                 cplusplus_function_mangled_name("test_function", {"test_namespace", "test_namespace"}, Int(32),
                                                 {ExternFuncArgument(make_zero(qual1_type)),
                                                  ExternFuncArgument(make_zero(qual2_type))},
                                                 target));

    check_result(expecteds, expecteds_index, target,
                 cplusplus_function_mangled_name("test_function", {"test_namespace", "test_namespace"}, Int(32),
                                                 {ExternFuncArgument(make_zero(test_type)),
                                                  ExternFuncArgument(make_zero(test_type))},
                                                 target));

    check_result(expecteds, expecteds_index, target,
                 cplusplus_function_mangled_name("test_function", {"std"}, Int(32), {}, target));

    check_result(expecteds, expecteds_index, target,
                 cplusplus_function_mangled_name("test_function", {"std", "foo"}, Int(32), {}, target));

    halide_handle_cplusplus_type std_enclosed_type_info(halide_handle_cplusplus_type(
        halide_cplusplus_type_name(halide_cplusplus_type_name::Struct, "test_struct"), {"std"},
        {halide_cplusplus_type_name(halide_cplusplus_type_name::Class, "enclosing_class")}));
    Type std_test_type(Handle(1, &std_enclosed_type_info));
    check_result(expecteds, expecteds_index, target,
                 cplusplus_function_mangled_name("test_function", {"std"}, Int(32),
                                                 {ExternFuncArgument(make_zero(std_test_type))}, target));

    halide_handle_cplusplus_type class_type_info(halide_handle_cplusplus_type(
        halide_cplusplus_type_name(halide_cplusplus_type_name::Class, "test_class"),
        {
            "test_namespace",
        },
        {}, {halide_handle_cplusplus_type::Pointer}));
    Type class_type(Handle(1, &class_type_info));
    check_result(expecteds, expecteds_index, target,
                 cplusplus_function_mangled_name("test_function", {"test_namespace", "test_namespace"}, Int(32),
                                                 {
                                                     ExternFuncArgument(make_zero(class_type)),
                                                 },
                                                 target));

    halide_handle_cplusplus_type union_type_info(halide_handle_cplusplus_type(
        halide_cplusplus_type_name(halide_cplusplus_type_name::Union, "test_union"),
        {
            "test_namespace",
        },
        {}, {halide_handle_cplusplus_type::Pointer}));
    Type union_type(Handle(1, &union_type_info));
    check_result(expecteds, expecteds_index, target,
                 cplusplus_function_mangled_name("test_function", {"test_namespace", "test_namespace"}, Int(32),
                                                 {
                                                     ExternFuncArgument(make_zero(union_type)),
                                                 },
                                                 target));

    halide_handle_cplusplus_type enum_type_info(halide_handle_cplusplus_type(
        halide_cplusplus_type_name(halide_cplusplus_type_name::Class, "test_enum"),
        {
            "test_namespace",
        },
        {}, {halide_handle_cplusplus_type::Pointer}));
    Type enum_type(Handle(1, &enum_type_info));
    check_result(expecteds, expecteds_index, target,
                 cplusplus_function_mangled_name("test_function", {"test_namespace", "test_namespace"}, Int(32),
                                                 {
                                                     ExternFuncArgument(make_zero(enum_type)),
                                                 },
                                                 target));
}

}  // namespace

void cplusplus_mangle_test() {
    Target targets[kTestTargetCount]{
        Target(Target::Linux, Target::X86, 32),
        Target(Target::Linux, Target::X86, 64),
        Target(Target::OSX, Target::X86, 32),
        Target(Target::OSX, Target::X86, 64),
        Target(Target::IOS, Target::ARM, 32),
        Target(Target::IOS, Target::ARM, 64),
        Target(Target::Windows, Target::X86, 32),
        Target(Target::Windows, Target::X86, 64)};
    MangleResult *expecteds[kTestTargetCount]{
        ItaniumABIMangling_main, ItaniumABIMangling_main,
        ItaniumABIMangling_main, ItaniumABIMangling_main,
        ItaniumABIMangling_main, ItaniumABIMangling_main,
        win32_expecteds, win64_expecteds};

    size_t i = 0;
    for (const auto &target : targets) {
        main_tests(expecteds[i++], target);
    }

    {
        // Test all primitive types.
        std::vector<ExternFuncArgument> args;
        args.emplace_back(make_zero(Bool()));
        args.emplace_back(make_zero(Int(8)));
        args.emplace_back(make_zero(UInt(8)));
        args.emplace_back(make_zero(Int(16)));
        args.emplace_back(make_zero(UInt(16)));
        args.emplace_back(make_zero(Int(32)));
        args.emplace_back(make_zero(UInt(32)));
        args.emplace_back(make_zero(Int(64)));
        args.emplace_back(make_zero(UInt(64)));
        args.emplace_back(make_zero(Float(32)));
        args.emplace_back(make_zero(Float(64)));

        size_t expecteds_index = 0;
        for (const auto &target : targets) {
            check_result(all_types_by_target, expecteds_index, target,
                         cplusplus_function_mangled_name("test_function", {}, Int(32), args, target));
        }
    }

    {
        // Test a whole ton of substitutions on type.
        std::vector<halide_handle_cplusplus_type> type_info;
        std::vector<ExternFuncArgument> args;
        for (int i = 0; i < 100; i++) {
            std::stringstream oss;
            oss << i;
            halide_handle_cplusplus_type t(halide_handle_cplusplus_type(
                halide_cplusplus_type_name(halide_cplusplus_type_name::Struct, "s" + oss.str()),
                {
                    "test_namespace",
                },
                {}, {halide_handle_cplusplus_type::Pointer}));
            type_info.push_back(t);
        }
        for (int i = 0; i < 200; i++) {
            args.emplace_back(make_zero(Handle(1, &type_info[i % 100])));
        }

        size_t expecteds_index = 0;
        for (const auto &target : targets) {
            check_result(many_type_subs, expecteds_index, target,
                         cplusplus_function_mangled_name("test_function", {}, Int(32), args, target));
        }
    }

    {
        // Test a whole ton of substitutions on names.
        std::vector<halide_handle_cplusplus_type> type_info;
        std::vector<ExternFuncArgument> args;
        for (int i = 0; i < 25; i++) {
            std::stringstream oss;
            oss << i;
            halide_handle_cplusplus_type t(halide_handle_cplusplus_type(
                halide_cplusplus_type_name(halide_cplusplus_type_name::Struct, "s"),
                {
                    "test_namespace" + oss.str(),
                },
                {}, {halide_handle_cplusplus_type::Pointer}));
            type_info.push_back(t);
        }
        for (int i = 0; i < 50; i++) {
            args.emplace_back(make_zero(Handle(1, &type_info[i % 25])));
        }

        size_t expecteds_index = 0;
        for (const auto &target : targets) {
            check_result(many_name_subs, expecteds_index, target,
                         cplusplus_function_mangled_name("test_function", {}, Int(32), args, target));
        }
    }

    {
        // Stack up a bunch of pointers and qualifiers.
        // int test_function(int * const, int *const*const, int *const*const*const*, ...);
        std::vector<halide_handle_cplusplus_type> type_info;
        std::vector<ExternFuncArgument> args;
        for (size_t i = 1; i <= 8; i++) {
            std::vector<uint8_t> mods;
            for (size_t j = 0; j < i; j++) {
                mods.push_back(halide_handle_cplusplus_type::Pointer | halide_handle_cplusplus_type::Const);
            }
            halide_handle_cplusplus_type t(halide_handle_cplusplus_type(
                halide_cplusplus_type_name(halide_cplusplus_type_name::Simple, "int32_t"),
                {}, {}, mods));
            type_info.push_back(t);
        }
        for (const auto &ti : type_info) {
            args.emplace_back(make_zero(Handle(1, &ti)));
        }
        size_t expecteds_index = 0;
        for (const auto &target : targets) {
            check_result(stacked_indirections, expecteds_index, target,
                         cplusplus_function_mangled_name("test_function", {}, Int(32), args, target));
        }
    }

    {
        // Test all qualifiers and all ref arguments
        for (const auto &target : targets) {
            size_t expecteds_index = 0;
            for (uint8_t mods = 0; mods < 16; mods++) {
                halide_handle_cplusplus_type t1(halide_handle_cplusplus_type(
                    halide_cplusplus_type_name(halide_cplusplus_type_name::Struct, "s"), {}, {}, {mods}));
                halide_handle_cplusplus_type t2(halide_handle_cplusplus_type(
                    halide_cplusplus_type_name(halide_cplusplus_type_name::Struct, "s"), {}, {}, {mods}, halide_handle_cplusplus_type::LValueReference));
                halide_handle_cplusplus_type t3(halide_handle_cplusplus_type(
                    halide_cplusplus_type_name(halide_cplusplus_type_name::Struct, "s"), {}, {}, {mods}, halide_handle_cplusplus_type::RValueReference));
                std::vector<ExternFuncArgument> args;
                args.emplace_back(make_zero(Handle(1, &t1)));
                args.emplace_back(make_zero(Handle(1, &t2)));
                args.emplace_back(make_zero(Handle(1, &t3)));

                MangleResult *expecteds = (target.os == Target::Windows) ? (target.bits == 64 ? all_mods_win64 : all_mods_win32) : all_mods_itanium;
                check_result(expecteds, expecteds_index, target,
                             cplusplus_function_mangled_name("test_function", {}, Int(32), args, target));
            }
        }
    }

    {
        // Test two void * arguments to ensure substititon handles void * correctly.
        // (This is a special case as "void *" is represented using nullptr for the type info.)
        for (const auto &target : targets) {
            size_t expecteds_index = 0;
            std::vector<ExternFuncArgument> args;
            args.emplace_back(make_zero(Handle(1, nullptr)));
            args.emplace_back(make_zero(Handle(1, nullptr)));

            MangleResult *expecteds = (target.os == Target::Windows) ? (target.bits == 64 ? two_void_stars_win64 : two_void_stars_win32) : two_void_stars_itanium;
            check_result(expecteds, expecteds_index, target,
                         cplusplus_function_mangled_name("test_function", {}, Int(32), args, target));
        }
    }
}

}  // namespace Internal

}  // namespace Halide
