Add a 'normalize' method to the Triple class, which takes a mucked up
authorDuncan Sands <baldrick@free.fr>
Thu, 12 Aug 2010 11:31:39 +0000 (11:31 +0000)
committerDuncan Sands <baldrick@free.fr>
Thu, 12 Aug 2010 11:31:39 +0000 (11:31 +0000)
target triple and straightens it out.  This does less than gcc's script
config.sub, for example it turns i386-mingw32 into i386--mingw32 not
i386-pc-mingw32, but it does a decent job of turning funky triples into
something that the rest of the Triple class can understand.  The plan
is to use this to canonicalize triple's when they are first provided
by users, and have the rest of LLVM only deal with canonical triples.
Once this is done the special case workarounds in the Triple constructor
can be removed, making the class more regular and easier to use.  The
comments and unittests for the Triple class are already adjusted in this
patch appropriately for this brave new world of increased uniformity.

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@110909 91177308-0d34-0410-b5e6-96231b3b80d8

include/llvm/ADT/Triple.h
lib/Support/Triple.cpp
unittests/ADT/TripleTest.cpp

index feade6a56fbdc572895cde1bc8a0d6b7152c966c..8dca3c1cfb1b8faa28af62b29db1b33712321bbb 100644 (file)
@@ -24,7 +24,7 @@ class Twine;
 
 /// Triple - Helper class for working with target triples.
 ///
-/// Target triples are strings in the format of:
+/// Target triples are strings in the canonical form:
 ///   ARCHITECTURE-VENDOR-OPERATING_SYSTEM
 /// or
 ///   ARCHITECTURE-VENDOR-OPERATING_SYSTEM-ENVIRONMENT
@@ -35,20 +35,11 @@ class Twine;
 /// from the components of the target triple to well known IDs.
 ///
 /// At its core the Triple class is designed to be a wrapper for a triple
-/// string; it does not normally change or normalize the triple string, instead
-/// it provides additional APIs to parse normalized parts out of the triple.
+/// string; the constructor does not change or normalize the triple string.
+/// Clients that need to handle the non-canonical triples that users often
+/// specify should use the normalize method.
 ///
-/// One curiosity this implies is that for some odd triples the results of,
-/// e.g., getOSName() can be very different from the result of getOS().  For
-/// example, for 'i386-mingw32', getOS() will return MinGW32, but since
-/// getOSName() is purely based on the string structure that will return the
-/// empty string.
-///
-/// Clients should generally avoid using getOSName() and related APIs unless
-/// they are familiar with the triple format (this is particularly true when
-/// rewriting a triple).
-///
-/// See autoconf/config.guess for a glimpse into what they look like in
+/// See autoconf/config.guess for a glimpse into what triples look like in
 /// practice.
 class Triple {
 public:
@@ -117,6 +108,9 @@ private:
   mutable OSType OS;
 
   bool isInitialized() const { return Arch != InvalidArch; }
+  static ArchType ParseArch(StringRef ArchName);
+  static VendorType ParseVendor(StringRef VendorName);
+  static OSType ParseOS(StringRef OSName);
   void Parse() const;
 
 public:
@@ -133,6 +127,16 @@ public:
     Data += OSStr;
   }
 
+  /// @}
+  /// @name Normalization
+  /// @{
+
+  /// normalize - Turn an arbitrary machine specification into the canonical
+  /// triple form (or something sensible that the Triple class understands if
+  /// nothing better can reasonably be done).  In particular, it handles the
+  /// common case in which otherwise valid components are in the wrong order.
+  static std::string normalize(StringRef Str);
+
   /// @}
   /// @name Typed Component Access
   /// @{
index 6a70449b56dc7d38ac6bd2e38948f6f0a7924794..7806aec16c6d9a4f590eaeb038e4b916e2eedc56 100644 (file)
@@ -221,121 +221,254 @@ const char *Triple::getArchNameForAssembler() {
 
 //
 
-void Triple::Parse() const {
-  assert(!isInitialized() && "Invalid parse call.");
-
-  StringRef ArchName = getArchName();
-  StringRef VendorName = getVendorName();
-  StringRef OSName = getOSName();
-
+Triple::ArchType Triple::ParseArch(StringRef ArchName) {
   if (ArchName.size() == 4 && ArchName[0] == 'i' && 
       ArchName[2] == '8' && ArchName[3] == '6' && 
       ArchName[1] - '3' < 6) // i[3-9]86
-    Arch = x86;
+    return x86;
   else if (ArchName == "amd64" || ArchName == "x86_64")
-    Arch = x86_64;
+    return x86_64;
   else if (ArchName == "bfin")
-    Arch = bfin;
+    return bfin;
   else if (ArchName == "pic16")
-    Arch = pic16;
+    return pic16;
   else if (ArchName == "powerpc")
-    Arch = ppc;
+    return ppc;
   else if ((ArchName == "powerpc64") || (ArchName == "ppu"))
-    Arch = ppc64;
+    return ppc64;
   else if (ArchName == "mblaze")
-    Arch = mblaze;
+    return mblaze;
   else if (ArchName == "arm" ||
            ArchName.startswith("armv") ||
            ArchName == "xscale")
-    Arch = arm;
+    return arm;
   else if (ArchName == "thumb" ||
            ArchName.startswith("thumbv"))
-    Arch = thumb;
+    return thumb;
   else if (ArchName.startswith("alpha"))
-    Arch = alpha;
+    return alpha;
   else if (ArchName == "spu" || ArchName == "cellspu")
-    Arch = cellspu;
+    return cellspu;
   else if (ArchName == "msp430")
-    Arch = msp430;
+    return msp430;
   else if (ArchName == "mips" || ArchName == "mipsallegrex")
-    Arch = mips;
+    return mips;
   else if (ArchName == "mipsel" || ArchName == "mipsallegrexel" ||
            ArchName == "psp")
-    Arch = mipsel;
+    return mipsel;
   else if (ArchName == "sparc")
-    Arch = sparc;
+    return sparc;
   else if (ArchName == "sparcv9")
-    Arch = sparcv9;
+    return sparcv9;
   else if (ArchName == "s390x")
-    Arch = systemz;
+    return systemz;
   else if (ArchName == "tce")
-    Arch = tce;
+    return tce;
   else if (ArchName == "xcore")
-    Arch = xcore;
+    return xcore;
   else
-    Arch = UnknownArch;
-
-
-  // Handle some exceptional cases where the OS / environment components are
-  // stuck into the vendor field.
-  if (StringRef(getTriple()).count('-') == 1) {
-    StringRef VendorName = getVendorName();
-
-    if (VendorName.startswith("mingw32")) { // 'i386-mingw32', etc.
-      Vendor = PC;
-      OS = MinGW32;
-      return;
-    }
-
-    // arm-elf is another example, but we don't currently parse anything about
-    // the environment.
-  }
+    return UnknownArch;
+}
 
+Triple::VendorType Triple::ParseVendor(StringRef VendorName) {
   if (VendorName == "apple")
-    Vendor = Apple;
+    return Apple;
   else if (VendorName == "pc")
-    Vendor = PC;
+    return PC;
   else
-    Vendor = UnknownVendor;
+    return UnknownVendor;
+}
 
+Triple::OSType Triple::ParseOS(StringRef OSName) {
   if (OSName.startswith("auroraux"))
-    OS = AuroraUX;
+    return AuroraUX;
   else if (OSName.startswith("cygwin"))
-    OS = Cygwin;
+    return Cygwin;
   else if (OSName.startswith("darwin"))
-    OS = Darwin;
+    return Darwin;
   else if (OSName.startswith("dragonfly"))
-    OS = DragonFly;
+    return DragonFly;
   else if (OSName.startswith("freebsd"))
-    OS = FreeBSD;
+    return FreeBSD;
   else if (OSName.startswith("linux"))
-    OS = Linux;
+    return Linux;
   else if (OSName.startswith("lv2"))
-    OS = Lv2;
+    return Lv2;
   else if (OSName.startswith("mingw32"))
-    OS = MinGW32;
+    return MinGW32;
   else if (OSName.startswith("mingw64"))
-    OS = MinGW64;
+    return MinGW64;
   else if (OSName.startswith("netbsd"))
-    OS = NetBSD;
+    return NetBSD;
   else if (OSName.startswith("openbsd"))
-    OS = OpenBSD;
+    return OpenBSD;
   else if (OSName.startswith("psp"))
-    OS = Psp;
+    return Psp;
   else if (OSName.startswith("solaris"))
-    OS = Solaris;
+    return Solaris;
   else if (OSName.startswith("win32"))
-    OS = Win32;
+    return Win32;
   else if (OSName.startswith("haiku"))
-    OS = Haiku;
+    return Haiku;
   else if (OSName.startswith("minix"))
-    OS = Minix;
+    return Minix;
   else
-    OS = UnknownOS;
+    return UnknownOS;
+}
+
+void Triple::Parse() const {
+  assert(!isInitialized() && "Invalid parse call.");
+
+  Arch = ParseArch(getArchName());
+  Vendor = ParseVendor(getVendorName());
+  OS = ParseOS(getOSName());
+
+  // Handle some exceptional cases where the OS / environment components are
+  // stuck into the vendor field.
+  // TODO: Remove this logic and have places that need it use 'normalize'.
+  if (StringRef(getTriple()).count('-') == 1) {
+    StringRef VendorName = getVendorName();
+
+    if (VendorName.startswith("mingw32")) { // 'i386-mingw32', etc.
+      Vendor = PC;
+      OS = MinGW32;
+      return;
+    }
+
+    // arm-elf is another example, but we don't currently parse anything about
+    // the environment.
+  }
 
   assert(isInitialized() && "Failed to initialize!");
 }
 
+std::string Triple::normalize(StringRef Str) {
+  // Parse into components.
+  SmallVector<StringRef, 4> Components;
+  for (size_t First = 0, Last = 0; Last != StringRef::npos; First = Last + 1) {
+    Last = Str.find('-', First);
+    Components.push_back(Str.slice(First, Last));
+  }
+
+  // If the first component corresponds to a known architecture, preferentially
+  // use it for the architecture.  If the second component corresponds to a
+  // known vendor, preferentially use it for the vendor, etc.  This avoids silly
+  // component movement when a component parses as (eg) both a valid arch and a
+  // valid os.
+  ArchType Arch = UnknownArch;
+  if (Components.size() > 0)
+    Arch = ParseArch(Components[0]);
+  VendorType Vendor = UnknownVendor;
+  if (Components.size() > 1)
+    Vendor = ParseVendor(Components[1]);
+  OSType OS = UnknownOS;
+  if (Components.size() > 2)
+    OS = ParseOS(Components[2]);
+
+  // Note which components are already in their final position.  These will not
+  // be moved.
+  bool Found[3];
+  Found[0] = Arch != UnknownArch;
+  Found[1] = Vendor != UnknownVendor;
+  Found[2] = OS != UnknownOS;
+
+  // If they are not there already, permute the components into their canonical
+  // positions by seeing if they parse as a valid architecture, and if so moving
+  // the component to the architecture position etc.
+  for (unsigned Pos = 0; Pos != 3; ++Pos) {
+    if (Found[Pos])
+      continue; // Already in the canonical position.
+
+    for (unsigned Idx = 0; Idx != Components.size(); ++Idx) {
+      // Do not reparse any components that already matched.
+      if (Idx < 3 && Found[Idx])
+        continue;
+
+      // Does this component parse as valid for the target position?
+      bool Valid = false;
+      StringRef Comp = Components[Idx];
+      switch (Pos) {
+      default:
+        assert(false && "unexpected component type!");
+      case 0:
+        Arch = ParseArch(Comp);
+        Valid = Arch != UnknownArch;
+        break;
+      case 1:
+        Vendor = ParseVendor(Comp);
+        Valid = Vendor != UnknownVendor;
+        break;
+      case 2:
+        OS = ParseOS(Comp);
+        Valid = OS != UnknownOS;
+        break;
+      }
+      if (!Valid)
+        continue; // Nope, try the next component.
+
+      // Move the component to the target position, pushing any non-fixed
+      // components that are in the way to the right.  This tends to give
+      // good results in the common cases of a forgotten vendor component
+      // or a wrongly positioned environment.
+      if (Pos < Idx) {
+        // Insert left, pushing the existing components to the right.  For
+        // example, a-b-i386 -> i386-a-b when moving i386 to the front.
+        StringRef CurrentComponent(""); // The empty component.
+        // Replace the component we are moving with an empty component.
+        std::swap(CurrentComponent, Components[Idx]);
+        // Insert the component being moved at Pos, displacing any existing
+        // components to the right.
+        for (unsigned i = Pos; !CurrentComponent.empty(); ++i) {
+          // Skip over any fixed components.
+          while (i < 3 && Found[i]) ++i;
+          // Place the component at the new position, getting the component
+          // that was at this position - it will be moved right.
+          std::swap(CurrentComponent, Components[i]);
+        }
+      } else if (Pos > Idx) {
+        // Push right by inserting empty components until the component at Idx
+        // reaches the target position Pos.  For example, pc-a -> -pc-a when
+        // moving pc to the second position.
+        do {
+          // Insert one empty component at Idx.
+          StringRef CurrentComponent(""); // The empty component.
+          for (unsigned i = Idx; i < Components.size(); ++i) {
+            // Skip over any fixed components.
+            while (i < 3 && Found[i]) ++i;
+            // Place the component at the new position, getting the component
+            // that was at this position - it will be moved right.
+            std::swap(CurrentComponent, Components[i]);
+            // If it was placed on top of an empty component then we are done.
+            if (CurrentComponent.empty())
+              break;
+          }
+          // The last component was pushed off the end - append it.
+          if (!CurrentComponent.empty())
+            Components.push_back(CurrentComponent);
+
+          // Advance Idx to the component's new position.
+          while (++Idx < 3 && Found[Idx]) {}
+        } while (Idx < Pos); // Add more until the final position is reached.
+      }
+      assert(Pos < Components.size() && Components[Pos] == Comp &&
+             "Component moved wrong!");
+      Found[Pos] = true;
+      break;
+    }
+  }
+
+  // Special case logic goes here.  At this point Arch, Vendor and OS have the
+  // correct values for the computed components.
+
+  // Stick the corrected components back together to form the normalized string.
+  std::string Normalized;
+  for (unsigned i = 0, e = Components.size(); i != e; ++i) {
+    if (i) Normalized += '-';
+    Normalized += Components[i];
+  }
+  return Normalized;
+}
+
 StringRef Triple::getArchName() const {
   return StringRef(Data).split('-').first;           // Isolate first component
 }
index 1a9e81a0df749e45eb9e8992599667c8c488ea07..067f5e5116cdd207043e61b6fa2ba648c3eae2a0 100644 (file)
@@ -92,18 +92,117 @@ TEST(TripleTest, ParsedIDs) {
 
   T = Triple("huh");
   EXPECT_EQ(Triple::UnknownArch, T.getArch());
+}
 
-  // Two exceptional cases.
+static std::string Join(StringRef A, StringRef B, StringRef C) {
+  std::string Str = A; Str += '-'; Str += B; Str += '-'; Str += C;
+  return Str;
+}
 
-  T = Triple("i386-mingw32");
-  EXPECT_EQ(Triple::x86, T.getArch());
-  EXPECT_EQ(Triple::PC, T.getVendor());
-  EXPECT_EQ(Triple::MinGW32, T.getOS());
+static std::string Join(StringRef A, StringRef B, StringRef C, StringRef D) {
+  std::string Str = A; Str += '-'; Str += B; Str += '-'; Str += C; Str += '-';
+  Str += D; return Str;
+}
 
-  T = Triple("arm-elf");
-  EXPECT_EQ(Triple::arm, T.getArch());
-  EXPECT_EQ(Triple::UnknownVendor, T.getVendor());
-  EXPECT_EQ(Triple::UnknownOS, T.getOS());
+TEST(TripleTest, Normalization) {
+  EXPECT_EQ("", Triple::normalize(""));
+  EXPECT_EQ("-", Triple::normalize("-"));
+  EXPECT_EQ("--", Triple::normalize("--"));
+  EXPECT_EQ("---", Triple::normalize("---"));
+  EXPECT_EQ("----", Triple::normalize("----"));
+
+  EXPECT_EQ("a", Triple::normalize("a"));
+  EXPECT_EQ("a-b", Triple::normalize("a-b"));
+  EXPECT_EQ("a-b-c", Triple::normalize("a-b-c"));
+  EXPECT_EQ("a-b-c-d", Triple::normalize("a-b-c-d"));
+
+  EXPECT_EQ("i386-b-c", Triple::normalize("i386-b-c"));
+  EXPECT_EQ("i386-a-c", Triple::normalize("a-i386-c"));
+  EXPECT_EQ("i386-a-b", Triple::normalize("a-b-i386"));
+
+  EXPECT_EQ("a-pc-c", Triple::normalize("a-pc-c"));
+  EXPECT_EQ("-pc-b-c", Triple::normalize("pc-b-c"));
+  EXPECT_EQ("a-pc-b", Triple::normalize("a-b-pc"));
+
+  EXPECT_EQ("a-b-linux", Triple::normalize("a-b-linux"));
+  EXPECT_EQ("--linux-b-c", Triple::normalize("linux-b-c"));
+  EXPECT_EQ("a--linux-c", Triple::normalize("a-linux-c"));
+
+  EXPECT_EQ("i386-pc-a", Triple::normalize("a-pc-i386"));
+  EXPECT_EQ("i386-pc-", Triple::normalize("-pc-i386"));
+  EXPECT_EQ("-pc-linux-c", Triple::normalize("linux-pc-c"));
+  EXPECT_EQ("-pc-linux", Triple::normalize("linux-pc-"));
+
+  EXPECT_EQ("i386", Triple::normalize("i386"));
+  EXPECT_EQ("-pc", Triple::normalize("pc"));
+  EXPECT_EQ("--linux", Triple::normalize("linux"));
+
+  // Check that normalizing a permutated set of valid components returns a
+  // triple with the unpermuted components.
+  StringRef C[4];
+  C[3] = "environment";
+  for (int Arch = 1+Triple::UnknownArch; Arch < Triple::InvalidArch; ++Arch) {
+    C[0] = Triple::getArchTypeName(Triple::ArchType(Arch));
+    for (int Vendor = 1+Triple::UnknownVendor; Vendor <= Triple::PC;
+         ++Vendor) {
+      C[1] = Triple::getVendorTypeName(Triple::VendorType(Vendor));
+      for (int OS = 1+Triple::UnknownOS; OS <= Triple::Minix; ++OS) {
+        C[2] = Triple::getOSTypeName(Triple::OSType(OS));
+
+        std::string E = Join(C[0], C[1], C[2]);
+        std::string F = Join(C[0], C[1], C[2], C[3]);
+        EXPECT_EQ(E, Triple::normalize(Join(C[0], C[1], C[2])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[0], C[1], C[2], C[3])));
+
+        // If a value has multiple interpretations, then the permutation
+        // test will inevitably fail.  Currently this is only the case for
+        // "psp" which parses as both an architecture and an O/S.
+        if (OS == Triple::Psp)
+          continue;
+
+        EXPECT_EQ(E, Triple::normalize(Join(C[0], C[2], C[1])));
+        EXPECT_EQ(E, Triple::normalize(Join(C[1], C[2], C[0])));
+        EXPECT_EQ(E, Triple::normalize(Join(C[1], C[0], C[2])));
+        EXPECT_EQ(E, Triple::normalize(Join(C[2], C[0], C[1])));
+        EXPECT_EQ(E, Triple::normalize(Join(C[2], C[1], C[0])));
+
+        EXPECT_EQ(F, Triple::normalize(Join(C[0], C[1], C[3], C[2])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[0], C[2], C[3], C[1])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[0], C[2], C[1], C[3])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[0], C[3], C[1], C[2])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[0], C[3], C[2], C[1])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[1], C[2], C[3], C[0])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[1], C[2], C[0], C[3])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[1], C[3], C[0], C[2])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[1], C[3], C[2], C[0])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[1], C[0], C[2], C[3])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[1], C[0], C[3], C[2])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[2], C[3], C[0], C[1])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[2], C[3], C[1], C[0])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[2], C[0], C[1], C[3])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[2], C[0], C[3], C[1])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[2], C[1], C[3], C[0])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[2], C[1], C[0], C[3])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[3], C[0], C[1], C[2])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[3], C[0], C[2], C[1])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[3], C[1], C[2], C[0])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[3], C[1], C[0], C[2])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[3], C[2], C[0], C[1])));
+        EXPECT_EQ(F, Triple::normalize(Join(C[3], C[2], C[1], C[0])));
+      }
+    }
+  }
+
+  EXPECT_EQ("a-b-psp", Triple::normalize("a-b-psp"));
+  EXPECT_EQ("psp-b-c", Triple::normalize("psp-b-c"));
+
+  // Various real-world funky triples.  The value returned by GCC's config.sub
+  // is given in the comment.
+  EXPECT_EQ("i386--mingw32", Triple::normalize("i386-mingw32")); // i386-pc-mingw32
+  EXPECT_EQ("x86_64--linux-gnu", Triple::normalize("x86_64-linux-gnu")); // x86_64-pc-linux-gnu
+  EXPECT_EQ("i486--linux-gnu", Triple::normalize("i486-linux-gnu")); // i486-pc-linux-gnu
+  EXPECT_EQ("i386-redhat-linux", Triple::normalize("i386-redhat-linux")); // i386-redhat-linux-gnu
+  EXPECT_EQ("i686--linux", Triple::normalize("i686-linux")); // i686-pc-linux-gnu
 }
 
 TEST(TripleTest, MutateName) {