Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 38 additions & 20 deletions lldb/source/Core/Mangled.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,41 @@ void Mangled::SetValue(ConstString name) {
}
}

// BEGIN SWIFT
#ifdef LLDB_ENABLE_SWIFT
std::pair<ConstString, DemangledNameInfo>
GetSwiftDemangledStr(ConstString m_mangled, const SymbolContext *sc,
ConstString &m_demangled) {
const char *mangled_name = m_mangled.AsCString("");
Log *log = GetLog(LLDBLog::Demangle);
LLDB_LOGF(log, "demangle swift: %s", mangled_name);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because otherwise this will crash on a nullptr

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, thanks 👍

auto [demangled, info] = SwiftLanguageRuntime::TrackedDemangleSymbolAsString(
mangled_name, SwiftLanguageRuntime::eTypeName, sc);
info.PrefixRange.second =
std::min(info.BasenameRange.first, info.ArgumentsRange.first);
info.SuffixRange.first =
std::max(info.BasenameRange.second, info.ArgumentsRange.second);
info.SuffixRange.second = demangled.length();

// Don't cache the demangled name if the function isn't available yet.
if (!sc || !sc->function) {
LLDB_LOGF(log, "demangle swift: %s -> \"%s\" (not cached)", mangled_name,
demangled.c_str());
return std::make_pair(ConstString(demangled), info);
}
if (demangled.empty()) {
LLDB_LOGF(log, "demangle swift: %s -> error: failed to demangle",
mangled_name);
} else {
LLDB_LOGF(log, "demangle swift: %s -> \"%s\"", mangled_name,
demangled.c_str());
m_demangled.SetStringWithMangledCounterpart(demangled, m_mangled);
}
return std::make_pair(m_demangled, info);
}
#endif // LLDB_ENABLE_SWIFT
// END SWIFT

// Local helpers for different demangling implementations.
static char *GetMSVCDemangledStr(llvm::StringRef M) {
char *demangled_cstr = llvm::microsoftDemangle(
Expand Down Expand Up @@ -350,26 +385,9 @@ ConstString Mangled::GetDemangledNameImpl(bool force, // BEGIN SWIFT
// explicitly unsupported on llvm.org.
#ifdef LLDB_ENABLE_SWIFT
{
const char *mangled_name = m_mangled.GetCString();
Log *log = GetLog(LLDBLog::Demangle);
LLDB_LOGF(log, "demangle swift: %s", mangled_name);
std::string demangled(SwiftLanguageRuntime::DemangleSymbolAsString(
mangled_name, SwiftLanguageRuntime::eTypeName, sc));
// Don't cache the demangled name the function isn't available yet.
if (!sc || !sc->function) {
LLDB_LOGF(log, "demangle swift: %s -> \"%s\" (not cached)", mangled_name,
demangled.c_str());
return ConstString(demangled);
}
if (demangled.empty()) {
LLDB_LOGF(log, "demangle swift: %s -> error: failed to demangle",
mangled_name);
} else {
LLDB_LOGF(log, "demangle swift: %s -> \"%s\"", mangled_name,
demangled.c_str());
m_demangled.SetStringWithMangledCounterpart(demangled, m_mangled);
}
return m_demangled;
auto demangled = GetSwiftDemangledStr(m_mangled, sc, m_demangled);
m_demangled_info.emplace(std::move(demangled.second));
return demangled.first;
}
#endif // LLDB_ENABLE_SWIFT
break;
Expand Down
122 changes: 105 additions & 17 deletions lldb/source/Plugins/Language/Swift/SwiftLanguage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1906,23 +1906,99 @@ SwiftLanguage::GetDemangledFunctionNameWithoutArguments(Mangled mangled) const {
return mangled_name;
}

static std::optional<llvm::StringRef>
static llvm::Expected<std::pair<llvm::StringRef, DemangledNameInfo>>
GetAndValidateInfo(const SymbolContext &sc) {
Mangled mangled = sc.GetPossiblyInlinedFunctionName();
if (!mangled)
return llvm::createStringError("Function does not have a mangled name.");

const char *mangled_name = mangled.GetMangledName().AsCString("");
auto [demangled_name, info] =
SwiftLanguageRuntime::TrackedDemangleSymbolAsString(
Copy link

@Michael137 Michael137 Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean we re-demangle the string every time? For the C++ plugin the demangled name info gets cached in the Mangled object. So mangled.GetDemangledInfo() is less expensive here. Any reason we do that differently in the Swift plugin?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is the use of SwiftLanguageRuntime::eTypeName in Mangled.cpp. Enabling syntax highlighting causes some code paths to flow through this function, whereas before syntax highlighting, they were flowing through another path which used SwiftLanguageRuntime::eSimplified.

This causes some tests to fail whether you use SwiftLanguageRuntime::eTypeName or SwiftLanguageRuntime:: eSimplified.

I am still experimenting with how to properly fix this.

Copy link
Author

@charles-zablit charles-zablit Jul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote a small prototype of what would be needed to have caching with different kinds of demangling verbosity (compact vs full) here: https://github.com/charles-zablit/llvm-project/compare/charles-zablit/lldb/swift-mangling...charles-zablit:llvm-project:charles-zablit/lldb/swift-mangling-experiment?expand=1

My main concern is that the caching system does not discriminate between demangling kinds: say you are demangling string foo with the compact scheme. This gets saved in the cache and then if you try to demangle with the full scheme, you would get the same result as for the compact scheme because the cache key does not contain the demangling scheme. Taking that scheme into account seems non trivial. I'm not sure if it's worth it in the scope of this PR.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the end, we will keep the NameFormatPreference enum, but only cache eFullName demangled functions, to keep the cache consistent.

mangled_name, SwiftLanguageRuntime::eSimplified, &sc);
info.PrefixRange.second =
std::min(info.BasenameRange.first, info.ArgumentsRange.first);
info.SuffixRange.first =
std::max(info.BasenameRange.second, info.ArgumentsRange.second);
info.SuffixRange.second = demangled_name.length();

if (demangled_name.empty())
return llvm::createStringError(
"Function '%s' does not have a demangled name.",
mangled.GetMangledName().AsCString(""));

// Function without a basename is nonsense.
if (!info.hasBasename())
return llvm::createStringError(
"DemangledInfo for '%s does not have basename range.",
demangled_name.data());

return std::make_pair(demangled_name, info);
}

static llvm::Expected<llvm::StringRef>
GetDemangledBasename(const SymbolContext &sc) {
return std::nullopt;
auto info_or_err = GetAndValidateInfo(sc);
if (!info_or_err)
return info_or_err.takeError();

auto [demangled_name, info] = *info_or_err;

return demangled_name.slice(info.BasenameRange.first,
info.BasenameRange.second);
}

static std::optional<llvm::StringRef>
static llvm::Expected<llvm::StringRef>
GetDemangledFunctionPrefix(const SymbolContext &sc) {
return std::nullopt;
auto info_or_err = GetAndValidateInfo(sc);
if (!info_or_err)
return info_or_err.takeError();

auto [demangled_name, info] = *info_or_err;

if (!info.hasPrefix())
return llvm::createStringError(
"DemangledInfo for '%s does not have suffix range.",
demangled_name.data());

return demangled_name.slice(info.PrefixRange.first, info.PrefixRange.second);
}

static std::optional<llvm::StringRef>
static llvm::Expected<llvm::StringRef>
GetDemangledFunctionSuffix(const SymbolContext &sc) {
return std::nullopt;
auto info_or_err = GetAndValidateInfo(sc);
if (!info_or_err)
return info_or_err.takeError();

auto [demangled_name, info] = *info_or_err;

if (!info.hasSuffix())
return llvm::createStringError(
"DemangledInfo for '%s does not have suffix range.",
demangled_name.data());

return demangled_name.slice(info.SuffixRange.first, info.SuffixRange.second);
}

static bool PrintDemangledArgumentList(Stream &s, const SymbolContext &sc) {
return false;
assert(sc.symbol);

auto info_or_err = GetAndValidateInfo(sc);
if (!info_or_err) {
LLDB_LOG_ERROR(GetLog(LLDBLog::Language), info_or_err.takeError(),
"Failed to handle ${{function.formatted-arguments}} "
"frame-format variable: {0}");
return false;
}
auto [demangled_name, info] = *info_or_err;

if (!info.hasArguments())
return false;

s << demangled_name.slice(info.ArgumentsRange.first,
info.ArgumentsRange.second);

return true;
}

static VariableListSP GetFunctionVariableList(const SymbolContext &sc) {
Expand All @@ -1941,11 +2017,15 @@ bool SwiftLanguage::HandleFrameFormatVariable(const SymbolContext &sc,
Stream &s) {
switch (type) {
case FormatEntity::Entry::Type::FunctionBasename: {
std::optional<llvm::StringRef> name = GetDemangledBasename(sc);
if (!name)
auto name_or_err = GetDemangledBasename(sc);
if (!name_or_err) {
LLDB_LOG_ERROR(
GetLog(LLDBLog::Language), name_or_err.takeError(),
"Failed to handle ${{function.basename}} frame-format variable: {0}");
return false;
}

s << *name;
s << *name_or_err;

return true;
}
Expand All @@ -1972,20 +2052,28 @@ bool SwiftLanguage::HandleFrameFormatVariable(const SymbolContext &sc,
return true;
}
case FormatEntity::Entry::Type::FunctionPrefix: {
std::optional<llvm::StringRef> prefix = GetDemangledFunctionPrefix(sc);
if (!prefix)
auto prefix_or_err = GetDemangledFunctionPrefix(sc);
if (!prefix_or_err) {
LLDB_LOG_ERROR(
GetLog(LLDBLog::Language), prefix_or_err.takeError(),
"Failed to handle ${{function.prefix}} frame-format variable: {0}");
return false;
}

s << *prefix;
s << *prefix_or_err;

return true;
}
case FormatEntity::Entry::Type::FunctionSuffix: {
std::optional<llvm::StringRef> suffix = GetDemangledFunctionSuffix(sc);
if (!suffix)
auto suffix_or_err = GetDemangledFunctionSuffix(sc);
if (!suffix_or_err) {
LLDB_LOG_ERROR(
GetLog(LLDBLog::Language), suffix_or_err.takeError(),
"Failed to handle ${{function.suffix}} frame-format variable: {0}");
return false;
}

s << *suffix;
s << *suffix_or_err;

return true;
}
Expand Down Expand Up @@ -2019,7 +2107,7 @@ class PluginProperties : public Properties {
}

FormatEntity::Entry GetFunctionNameFormat() const {
return GetPropertyAtIndexAs<const FormatEntity::Entry>(
return GetPropertyAtIndexAs<FormatEntity::Entry>(
ePropertyFunctionNameFormat, {});
}
};
Expand Down
97 changes: 97 additions & 0 deletions lldb/source/Plugins/Language/Swift/SwiftMangled.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//===-- SwiftMangled.h ------------------------------------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#ifndef liblldb_SwiftMangled_h_
#define liblldb_SwiftMangled_h_

#include "lldb/Core/DemangledNameInfo.h"
#include "swift/Demangling/Demangle.h"

using namespace swift::Demangle;

/// A NodePrinter class with range tracking capabilities.
///
/// When used instead of a regular NodePrinter, this class will store additional
/// range information of the demangled name in the `info` attribute, such as the
/// range of the name of a method.
class TrackingNodePrinter : public NodePrinter {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a oxygen comment here explaining very briefly what this class is used for?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed 👍

public:
TrackingNodePrinter(DemangleOptions options) : NodePrinter(options) {}

lldb_private::DemangledNameInfo getInfo() { return info; }

private:
lldb_private::DemangledNameInfo info;
std::optional<unsigned> parametersDepth;

void startName() {
if (!info.hasBasename())
info.BasenameRange.first = getStreamLength();
}

void endName() {
if (!info.hasBasename())
info.BasenameRange.second = getStreamLength();
}

void startParameters(unsigned depth) {
if (parametersDepth || !info.hasBasename() ||
info.ArgumentsRange.first < info.ArgumentsRange.second) {
return;
}
info.ArgumentsRange.first = getStreamLength();
parametersDepth = depth;
}

void endParameters(unsigned depth) {
if (!parametersDepth || *parametersDepth != depth ||
info.ArgumentsRange.first < info.ArgumentsRange.second) {
return;
}
info.ArgumentsRange.second = getStreamLength();
}

bool shouldTrackNameRange(NodePointer Node) const {
assert(Node);
switch (Node->getKind()) {
case Node::Kind::Function:
case Node::Kind::Constructor:
case Node::Kind::Allocator:
case Node::Kind::ExplicitClosure:
return true;
default:
return false;
}
}

void printFunctionName(bool hasName, llvm::StringRef &OverwriteName,
llvm::StringRef &ExtraName, bool MultiWordName,
int &ExtraIndex, NodePointer Entity,
unsigned int depth) override {
if (shouldTrackNameRange(Entity))
startName();
NodePrinter::printFunctionName(hasName, OverwriteName, ExtraName,
MultiWordName, ExtraIndex, Entity, depth);
if (shouldTrackNameRange(Entity))
endName();
}

void printFunctionParameters(NodePointer LabelList, NodePointer ParameterType,
unsigned depth, bool showTypes) override {
startParameters(depth);
NodePrinter::printFunctionParameters(LabelList, ParameterType, depth,
showTypes);
endParameters(depth);
}
};

#endif // liblldb_SwiftMangled_h_
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,35 @@ class SwiftLanguageRuntime : public LanguageRuntime {
IsSwiftAsyncAwaitResumePartialFunctionSymbol(llvm::StringRef name);

enum DemangleMode { eSimplified, eTypeName, eDisplayTypeName };

/// Demangle a symbol to a string.
///
/// \param symbol The mangled symbol to demangle.
/// \param mode The `DemangleMode` to use when demangling.
/// \param sc The associated `SymbolContext`.
/// \param exe_ctx The associated `ExecutionContext`.
///
/// \return The demangled symbol.
static std::string
DemangleSymbolAsString(llvm::StringRef symbol, DemangleMode mode,
const SymbolContext *sc = nullptr,
const ExecutionContext *exe_ctx = nullptr);

static std::string GetParentNameIfClosure(Function &func);

/// Demangle a symbol to a string with additional range information.
///
/// \param symbol The mangled symbol to demangle.
/// \param mode The `DemangleMode` to use when demangling.
/// \param sc The associated `SymbolContext`.
/// \param exe_ctx The associated `ExecutionContext`.
///
/// \return The demangled symbol as well as range tracking information.
static std::pair<std::string, DemangledNameInfo>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doxygen comment?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed 👍

TrackedDemangleSymbolAsString(llvm::StringRef symbol, DemangleMode mode,
const SymbolContext *sc = nullptr,
const ExecutionContext *exe_ctx = nullptr);

/// Demangle a symbol to a swift::Demangle node tree.
///
/// This is a central point of access, for purposes such as logging.
Expand Down Expand Up @@ -885,6 +907,11 @@ class SwiftLanguageRuntime : public LanguageRuntime {

/// Swift native NSError isa.
std::optional<lldb::addr_t> m_SwiftNativeNSErrorISA;

static std::pair<std::string, std::optional<DemangledNameInfo>>
DemangleSymbolAsString(llvm::StringRef symbol, DemangleMode mode,
bool tracking, const SymbolContext *sc,
const ExecutionContext *exe_ctx);
};

/// The target specific register numbers used for async unwinding.
Expand Down
Loading