From d711321b851e018084ab1325d7ccadf2bf5b7ce6 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 20 Oct 2026 09:43:59 +0200 Subject: [PATCH] test(const-param): an empty parameter may take an IR argument, as on Linux arm64 --- test/unit/analyzer_module_unit_tests.cpp | 33 +++++++++++++++++++++--- test/unit/parameter_binding_input.cpp | 3 ++- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/test/unit/analyzer_module_unit_tests.cpp b/test/unit/analyzer_module_unit_tests.cpp index f8f7f5d..ef2a54e 100644 --- a/test/unit/analyzer_module_unit_tests.cpp +++ b/test/unit/analyzer_module_unit_tests.cpp @@ -2521,6 +2521,9 @@ namespace } return parameters; }; + // The arguments' bindings, in order. An expected entry written "?name: binding" is the + // parameter of an argument that only some ABIs pass: absent, or at that place with that + // binding, its IR name the parameter's or derived from it (tag.coerce). const auto check = [&](const LoadedModule& loaded, const char* symbol, const std::vector& expected) { @@ -2529,7 +2532,28 @@ namespace std::string text; for (const std::string& parameter : actual) text += " [" + parameter + "]"; - report.expect(actual == expected, + const auto passes = [](const std::string& parameter, const std::string& optional) + { + const std::size_t colon = optional.find(':'); + const std::string name = optional.substr(1, colon - 1); + return parameter == optional.substr(1) || + (parameter.starts_with(name + ".") && + parameter.substr(parameter.find(':')) == optional.substr(colon)); + }; + std::size_t next = 0; + bool matches = true; + for (const std::string& entry : expected) + { + if (entry.starts_with("?")) + { + if (next < actual.size() && passes(actual[next], entry)) + ++next; + continue; + } + matches = matches && next < actual.size() && actual[next] == entry; + ++next; + } + report.expect(matches && next == actual.size(), std::string("ParameterDebugBinding: ") + symbol + text); }; const auto load = @@ -2574,7 +2598,8 @@ namespace {".block_descriptor: artificial, source none", "x: int *, source 0", "y: double *, source 1"}); // A variadic function's debug type ends with a null entry for `...`, which no parameter - // takes; an empty parameter takes no IR argument, but keeps its debug declaration. + // takes; an empty parameter keeps its debug declaration, and takes an IR argument only on + // some ABIs (an i8 under AAPCS64 on Linux, none on x86-64 or Apple arm64). check(itanium, "_ZN7VarargsC2EPiPdz", {"this: artificial, source none", "vtt: artificial, source none", "x: int *, source 0", "y: double *, source 1"}); @@ -2583,7 +2608,7 @@ namespace "x: int *, source 0", "y: double *, source 1"}); check(itanium, "_ZN6TaggedC2EPi3Tag", {"this: artificial, source none", "vtt: artificial, source none", - "x: int *, source 0"}); + "x: int *, source 0", "?tag: Tag, source 1"}); check(itanium, "_Z8make_bigPiz", {"agg.result: artificial, source none", "p: int *, source 0"}); @@ -2598,7 +2623,7 @@ namespace // The empty parameter comes first: x is the second parameter the source declares. check(itanium, "_ZN8TagFirstC2E3TagPi", {"this: artificial, source none", "vtt: artificial, source none", - "x: int *, source 1"}); + "?tag: Tag, source 0", "x: int *, source 1"}); // Optimized code spills nothing: each argument is bound through its own debug records. check(optimized, "_ZN8VariadicC2EPiPdz", diff --git a/test/unit/parameter_binding_input.cpp b/test/unit/parameter_binding_input.cpp index 3551631..f2b828e 100644 --- a/test/unit/parameter_binding_input.cpp +++ b/test/unit/parameter_binding_input.cpp @@ -56,7 +56,8 @@ Varargs::Varargs(int* x, double* y, ...) seed = *x + static_cast(*y); } -// An empty parameter takes no IR argument, but keeps its debug declaration and its number. +// An empty parameter keeps its debug declaration and its number; whether it takes an IR argument +// depends on the ABI: none on x86-64 or Apple arm64, an i8 under AAPCS64 on Linux. struct Tag { };