bulbazord created this revision.
bulbazord added reviewers: JDevlieghere, mib, jingham, jasonmolenda, clayborg.
Herald added a project: All.
bulbazord requested review of this revision.
Herald added a project: LLDB.
Herald added a subscriber: lldb-commits.

Mutating a FileSpec is quite expensive. Every time you add or remove a
component of a path, we need to re-parse the entire path and insert a
new directory and filename into the ConstString StringPool. In many
cases, we take an existing FilePath and slowly morph it into the one we
want to actually use.

In order to improve performance, I want to introduce a new abstraction
called the `FileSpecBuilder`. The idea is that it maintains a
SmallString to store the entire path and a path style. It uses llvm's
path manipulation code to quickly append and remove things so we don't
have to re-parse the path every single time. When you're done
manipulating paths and want a FileSpec for sure, you can invoke
`FileSpecBuilder::CreateFileSpec` to create a new FileSpec, only parsing
the path once.

This patch primarily is to gather feedback about the idea. I wanted to
keep this patch small and targeted to get specific feedback, and for
that reason I avoided doing any kind of refactoring as most changes will
be quite invasive. Instead I opted to add unit tests to show how I want
FileSpecBuilder to be used in code. If this abstraction goes into LLDB,
I will refactor every place where we mutate a FileSpec until
FileSpecBuilder is the default way of manipulating paths.

For further motivation of this change, please see:
https://reviews.llvm.org/D149096


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D151765

Files:
  lldb/include/lldb/Utility/FileSpecBuilder.h
  lldb/source/Utility/CMakeLists.txt
  lldb/source/Utility/FileSpecBuilder.cpp
  lldb/unittests/Utility/FileSpecBuilderTest.cpp

Index: lldb/unittests/Utility/FileSpecBuilderTest.cpp
===================================================================
--- /dev/null
+++ lldb/unittests/Utility/FileSpecBuilderTest.cpp
@@ -0,0 +1,118 @@
+//===-- FileSpecBuilderTest.cpp -------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "gtest/gtest.h"
+
+#include "lldb/Utility/FileSpec.h"
+#include "lldb/Utility/FileSpecBuilder.h"
+
+using namespace lldb_private;
+
+static FileSpec PosixSpec(llvm::StringRef path) {
+  return FileSpec(path, FileSpec::Style::posix);
+}
+
+static FileSpec WindowsSpec(llvm::StringRef path) {
+  return FileSpec(path, FileSpec::Style::windows);
+}
+
+TEST(FileSpecBuilderTest, AppendPathComponent) {
+  FileSpecBuilder from_scratch_posix("/", FileSpecBuilder::Style::posix);
+  EXPECT_EQ("/", from_scratch_posix.GetCurrentPath());
+  from_scratch_posix.AppendPathComponent("foo");
+  EXPECT_EQ("/foo", from_scratch_posix.GetCurrentPath());
+  from_scratch_posix.AppendPathComponent("bar");
+  EXPECT_EQ("/foo/bar", from_scratch_posix.GetCurrentPath());
+  from_scratch_posix.AppendPathComponent("baz");
+  EXPECT_EQ("/foo/bar/baz", from_scatch_posix.GetCurrentPath());
+  const FileSpec posix_scratch_result = from_scratch_posix.CreateFileSpec();
+  EXPECT_STREQ("/foo/bar/baz", posix_scratch_result.GetPath().c_str());
+
+  const FileSpec fs_posix = PosixSpec("/foo");
+  FileSpecBuilder builder_posix(fs_posix);
+  builder_posix.AppendPathComponent("bar");
+  EXPECT_EQ("/foo/bar", builder_posix.GetCurrentPath());
+  const FileSpec posix_result = builder_posix.CreateFileSpec();
+  EXPECT_STREQ("/foo/bar", posix_result.GetPath().c_str());
+
+  const FileSpec fs_windows = WindowsSpec("F:\\");
+  FileSpecBuilder builder_windows(fs_windows);
+  builder_windows.AppendPathComponent("bar");
+  EXPECT_EQ("F:\\bar", builder_windows.GetCurrentPath());
+  builder_windows.AppendPathComponent("baz");
+  EXPECT_EQ("F:\\bar\\baz", builder_windows.GetCurrentPath());
+  const FileSpec windows_result = builder_windows.CreateFileSpec();
+  EXPECT_STREQ("F:\\bar\\baz", windows_result.GetPath().c_str());
+}
+
+TEST(FileSpecBuilderTest, RemoveLastPathComponent) {
+  const FileSpec fs_posix = PosixSpec("/foo/bar/baz");
+  FileSpecBuilder builder_posix(fs_posix);
+
+  EXPECT_EQ("/foo/bar/baz", builder_posix.GetCurrentPath());
+  EXPECT_TRUE(fs_posix.RemoveLastPathComponent());
+  EXPECT_EQ("/foo/bar", builder_posix.GetCurrentPath());
+  EXPECT_TRUE(fs_posix.RemoveLastPathComponent());
+  EXPECT_EQ("/foo", builder_posix.GetCurrentPath());
+  EXPECT_TRUE(fs_posix.RemoveLastPathComponent());
+  EXPECT_EQ("/", builder_posix.GetCurrentPath());
+  EXPECT_FALSE(fs_posix.RemoveLastPathComponent());
+  EXPECT_EQ("/", builder_posix.GetCurrentPath());
+
+  const FileSpec fs_posix_relative = PosixSpec("./foo/bar/baz");
+  FileSpecBuilder builder_posix_relative(fs_posix_relative);
+  EXPECT_EQ("foo/bar/baz", builder_posix_relative.GetCurrentPath());
+  EXPECT_TRUE(fs_posix_relative.RemoveLastPathComponent());
+  EXPECT_EQ("foo/bar", builder_posix_relative.GetCurrentPath());
+  EXPECT_TRUE(fs_posix_relative.RemoveLastPathComponent());
+  EXPECT_EQ("foo", builder_posix_relative.GetCurrentPath());
+  EXPECT_FALSE(fs_posix_relative.RemoveLastPathComponent());
+  EXPECT_EQ("foo", builder_posix_relative.GetCurrentPath());
+
+  const FileSpec fs_posix_relative2 = PosixSpec("./");
+  FileSpecBuilder builder_posix_relative2(fs_posix_relative2);
+
+  EXPECT_EQ(".", builder_posix_relative2.GetCurrentPath());
+  EXPECT_FALSE(fs_posix_relative2.RemoveLastPathComponent());
+  EXPECT_EQ(".", builder_posix_relative2.GetCurrentPath());
+  EXPECT_FALSE(fs_posix_relative.RemoveLastPathComponent());
+  EXPECT_EQ(".", builder_posix_relative2.GetCurrentPath());
+
+  const FileSpec fs_windows = WindowsSpec("C:\\foo\\bar\\baz");
+  FileSpecBuilder builder_windows(fs_windows);
+  EXPECT_EQ("C:\\foo\\bar\\baz", builder_windows.GetCurrentPath());
+  EXPECT_TRUE(fs_windows.RemoveLastPathComponent());
+  EXPECT_EQ("C:\\foo\\bar", builder_windows.GetCurrentPath());
+  EXPECT_TRUE(fs_windows.RemoveLastPathComponent());
+  EXPECT_EQ("C:\\foo", builder_windows.GetCurrentPath());
+  EXPECT_TRUE(fs_windows.RemoveLastPathComponent());
+  EXPECT_EQ("C:\\", builder_windows.GetCurrentPath());
+  EXPECT_TRUE(fs_windows.RemoveLastPathComponent());
+  EXPECT_EQ("C:", builder_windows.GetCurrentPath());
+  EXPECT_FALSE(fs_windows.RemoveLastPathComponent());
+  EXPECT_EQ("C:", builder_windows.GetCurrentPath());
+}
+
+TEST(FileSpecBuilderTest, FindDsymNextToExecutable) {
+  const FileSpec exec = PosixSpec("/foo/bar/baz");
+  const llvm::StringRef name = exec.GetFilename();
+
+  FileSpecBuilder builder(exec);
+  builder.RemoveLastPathComponent();
+
+  const std::string dsym_name = name.str() + ".dSYM";
+  builder.AppendPathComponent(dsym_name);
+  builder.AppendPathComponent("Contents");
+  builder.AppendPathComponent("Resources");
+  builder.AppendPathComponent("DWARF");
+  builder.AppendPathComponent(name);
+
+  const FileSpec dsym_file = builder.CreateFileSpec();
+  EXPECT_STREQ("/foo/bar/baz.dSYM/Contents/Resources/DWARF/baz",
+               dsym_file.GetPath().c_str());
+}
Index: lldb/source/Utility/FileSpecBuilder.cpp
===================================================================
--- /dev/null
+++ lldb/source/Utility/FileSpecBuilder.cpp
@@ -0,0 +1,41 @@
+//===-- FileSpecBuilder.cpp -----------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Utility/FileSpecBuilder.h"
+
+#include "lldb/Utility/FileSpec.h"
+
+#include "llvm/ADT/StringRef.h"
+
+using namespace lldb_private;
+
+FileSpecBuilder::FileSpecBuilder() : m_path(), m_style(Style::native) {}
+
+FileSpecBuilder::FileSpecBuilder(const llvm::StringRef path, const Style style)
+    : m_path(path), m_style(style) {}
+
+FileSpecBuilder::FileSpecBuilder(const FileSpec &fspec)
+    : m_path(fspec.GetPath()), m_style(fspec.GetPathStyle()) {}
+
+const llvm::StringRef FileSpecBuilder::GetCurrentPath() const { return m_path; }
+
+void FileSpecBuilder::AppendPathComponent(const llvm::StringRef component) {
+  llvm::sys::path::append(m_path, m_style, component);
+}
+
+bool FileSpecBuilder::RemoveLastPathComponent() {
+  if (!llvm::sys::path::has_parent_path(m_path, m_style))
+    return false;
+
+  llvm::sys::path::remove_filename(m_path, m_style);
+  return true;
+}
+
+FileSpec FileSpecBuilder::CreateFileSpec() {
+  return FileSpec(m_path.str(), m_style);
+}
Index: lldb/source/Utility/CMakeLists.txt
===================================================================
--- lldb/source/Utility/CMakeLists.txt
+++ lldb/source/Utility/CMakeLists.txt
@@ -39,6 +39,7 @@
   Environment.cpp
   Event.cpp
   FileSpec.cpp
+  FileSpecBuilder.cpp
   FileSpecList.cpp
   GDBRemote.cpp
   IOObject.cpp
Index: lldb/include/lldb/Utility/FileSpecBuilder.h
===================================================================
--- /dev/null
+++ lldb/include/lldb/Utility/FileSpecBuilder.h
@@ -0,0 +1,90 @@
+//===-- FileSpecBuilder.h ---------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_UTILITY_FILESPECBUILDER_H
+#define LLDB_UTILITY_FILESPECBUILDER_H
+
+#include "lldb/Utility/FileSpec.h"
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+
+namespace lldb_private {
+
+/// \class FileSpecBuilder FileSpecBuilder.h "lldb/Utility/FileSpecBuilder.h"
+/// A utility for quickly building FileSpec objects
+///
+/// A class for quickly building FileSpec objects. FileSpec is a useful
+/// abstraction but mutating them can be an expensive operation. FileSpecBuilder
+/// is meant to be an abstraction for quickly creating a FileSpec object by
+/// mutating an existing FileSpec object.
+class FileSpecBuilder {
+public:
+  using Style = llvm::sys::path::Style;
+
+  /// Empty constructors
+  ///
+  /// Creates an FileSpecBuilder where the path is empty. The path style will
+  /// default to the host system's native path style.
+  FileSpecBuilder();
+
+  /// Constructor with path.
+  ///
+  /// Takes a path to a file. This can be a full path or just the filename.
+  ///
+  /// \param[in] path
+  ///     The full or partial path to a file.
+  ///
+  /// \param[in] style
+  ///     The style of the path
+  explicit FileSpecBuilder(const llvm::StringRef path,
+                           const Style style = Style::native);
+
+  /// Constructor with FileSpec.
+  ///
+  /// Takes an existing FileSpec and uses the path from the FileSpec.
+  ///
+  /// \param[in] fspec
+  ///   An existing FileSpec object.
+  explicit FileSpecBuilder(const FileSpec &fspec);
+
+  /// Returns the existing FileSpecBuilder's path.
+  ///
+  /// \return
+  ///   A StringRef to the FileSpecBuilder's current path
+  const llvm::StringRef GetCurrentPath() const;
+
+  /// Appends a new path component to the end of this FileSpecBuilder.
+  ///
+  /// \param[in] component
+  ///   A StringRef referring to the component.
+  void AppendPathComponent(const llvm::StringRef component);
+
+  /// Removes the last path component in the path.
+  ///
+  /// If there is only one component in the path, this will not change the path.
+  ///
+  /// \return
+  ///     A boolean value indicating whether the path was updated.
+  bool RemoveLastPathComponent();
+
+  /// Creates a FileSpec from this FileSpecBuilder.
+  ///
+  /// \return
+  ///   A FileSpec object using the path from this FileSpecBuilder.
+  FileSpec CreateFileSpec();
+
+protected:
+  llvm::SmallString<128> m_path;
+  Style m_style; ///< The syntax that this path uses (e.g. Windows / Posix)
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_UTILITY_FILESPECBUILDER_H
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to