[Openmp-commits] [openmp] [libomp] Add kmp_str_ref (ADT 1/2) (PR #176162)

Robert Imschweiler via Openmp-commits openmp-commits at lists.llvm.org
Wed Jan 28 07:12:34 PST 2026


https://github.com/ro-i updated https://github.com/llvm/llvm-project/pull/176162

>From 37a327d6d6fadf8aa4bbe271d69ba2269f123051 Mon Sep 17 00:00:00 2001
From: Robert Imschweiler <robert.imschweiler at amd.com>
Date: Thu, 15 Jan 2026 04:33:33 -0600
Subject: [PATCH 1/2] [libomp] Add kmp_str_ref (ADT 1/2)

libomp currently has two limitations:
1) although it's C++, it doesn't link against the C++ stdlib
2) it cannot link against the implementation of LLVM ADTs

These limitations shall not be altered at the moment.

As a result, this commit introduces kmp_str_ref, which is similar to
LLVM's StringRef. It currently only includes methods I need at the
moment, but it's easily extensible.
---
 openmp/runtime/src/kmp_adt.h                  | 141 +++++
 openmp/runtime/src/kmp_str.cpp                |   4 +-
 openmp/runtime/src/kmp_str.h                  |   3 +-
 openmp/runtime/unittests/ADT/CMakeLists.txt   |   4 +
 .../runtime/unittests/ADT/TestStringRef.cpp   | 567 ++++++++++++++++++
 openmp/runtime/unittests/CMakeLists.txt       |   1 +
 .../runtime/unittests/String/TestKmpStr.cpp   |  27 +
 7 files changed, 744 insertions(+), 3 deletions(-)
 create mode 100644 openmp/runtime/src/kmp_adt.h
 create mode 100644 openmp/runtime/unittests/ADT/CMakeLists.txt
 create mode 100644 openmp/runtime/unittests/ADT/TestStringRef.cpp

diff --git a/openmp/runtime/src/kmp_adt.h b/openmp/runtime/src/kmp_adt.h
new file mode 100644
index 0000000000000..35069d377e40e
--- /dev/null
+++ b/openmp/runtime/src/kmp_adt.h
@@ -0,0 +1,141 @@
+/*
+ * kmp_adt.h -- Advanced Data Types used internally
+ */
+
+//===----------------------------------------------------------------------===//
+//
+// 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 __KMP_ADT_H__
+#define __KMP_ADT_H__
+
+#include <cassert>
+#include <cctype>
+#include <cstddef>
+#include <cstring>
+
+#include "kmp.h"
+
+/// kmp_str_ref is a non-owning string class (similar to llvm::StringRef).
+class kmp_str_ref final {
+  const char *data;
+  size_t len;
+
+public:
+  kmp_str_ref(const char *data) : data(data), len(data ? strlen(data) : 0) {
+    assert(data && "kmp_str_ref cannot be constructed from nullptr");
+  }
+  kmp_str_ref(const char *data, size_t len) : data(data), len(len) {
+    assert(data && "kmp_str_ref cannot be constructed from nullptr");
+  }
+
+  kmp_str_ref(const kmp_str_ref &other) = default;
+  kmp_str_ref &operator=(const kmp_str_ref &other) = default;
+
+  // Check if the string starts with the given prefix and remove it from the
+  // string afterwards.
+  bool consume_front(kmp_str_ref prefix) {
+    if (len < prefix.len)
+      return false;
+    if (memcmp(data, prefix.data, prefix.len) != 0)
+      return false;
+    data += prefix.len;
+    len -= prefix.len;
+    return true;
+  }
+
+  // Start consuming an integer from the start of the string and remove it from
+  // the string afterwards.
+  // The maximum integer value that can currently be parsed is INT_MAX - 1.
+  bool consume_integer(int &value, bool allow_zero = true,
+                       bool allow_negative = false) {
+    kmp_str_ref orig = *this; // save state
+    bool is_negative = consume_front("-");
+    if (is_negative && !allow_negative) {
+      *this = orig;
+      return false;
+    }
+    size_t num_digits = count_while([](char c) {
+      return static_cast<bool>(isdigit(static_cast<unsigned char>(c)));
+    });
+    if (!num_digits) {
+      *this = orig;
+      return false;
+    }
+    assert(num_digits <= INT_MAX);
+    value = __kmp_basic_str_to_int(data, static_cast<int>(num_digits));
+    if (value == INT_MAX) {
+      *this = orig;
+      return false;
+    }
+    drop_front(num_digits);
+    if (is_negative)
+      value = -value;
+    if (!allow_zero && value == 0) {
+      *this = orig;
+      return false;
+    }
+    return true;
+  }
+
+  // Get an own duplicate of the string.
+  // Must be freed with KMP_INTERNAL_FREE().
+  char *copy() const {
+    char *copy_str = static_cast<char *>(KMP_INTERNAL_MALLOC(len + 1));
+    assert(copy_str && "copy() failed to allocate memory");
+    memcpy(copy_str, data, len);
+    copy_str[len] = '\0';
+    return copy_str;
+  }
+
+  // Count the number of characters in the string while the predicate returns
+  // true.
+  size_t count_while(bool (*predicate)(char)) const {
+    size_t i = 0;
+    while (i < len && predicate(data[i]))
+      ++i;
+    return i;
+  }
+
+  // Drop the first n characters from the string.
+  void drop_front(size_t n) {
+    if (n > len)
+      n = len;
+    data += n;
+    len -= n;
+  }
+
+  // Drop characters from the string while the predicate returns true.
+  void drop_while(bool (*predicate)(char)) {
+    drop_front(count_while(predicate));
+  }
+
+  // Check if the string is empty.
+  bool empty() const { return len == 0; }
+
+  // Get the length of the string.
+  size_t length() const { return len; }
+
+  // Drop space from the start of the string.
+  void skip_space() {
+    drop_while([](char c) {
+      return static_cast<bool>(isspace(static_cast<unsigned char>(c)));
+    });
+  }
+
+  // Construct a new string with the longest prefix of the original string that
+  // satisfies the predicate. Doesn't modify the original string.
+  kmp_str_ref take_while(bool (*predicate)(char)) const {
+    return kmp_str_ref(data, count_while(predicate));
+  }
+
+  // Iterator support (raw pointers work as iterators for contiguous storage)
+  const char *begin() const { return data; }
+  const char *end() const { return data + len; }
+};
+
+#endif // __KMP_ADT_H__
diff --git a/openmp/runtime/src/kmp_str.cpp b/openmp/runtime/src/kmp_str.cpp
index 12cce53074821..fc5d145b4a3a5 100644
--- a/openmp/runtime/src/kmp_str.cpp
+++ b/openmp/runtime/src/kmp_str.cpp
@@ -619,13 +619,13 @@ char *__kmp_str_token(
   return token;
 } // __kmp_str_token
 
-int __kmp_basic_str_to_int(char const *str) {
+int __kmp_basic_str_to_int(char const *str, int maxlen) {
   int result;
   char const *t;
 
   result = 0;
 
-  for (t = str; *t != '\0'; ++t) {
+  for (t = str; *t != '\0' && maxlen > 0; ++t, --maxlen) {
     if (*t < '0' || *t > '9')
       break;
     // Cap parsing to create largest integer
diff --git a/openmp/runtime/src/kmp_str.h b/openmp/runtime/src/kmp_str.h
index 11f633cd8024f..1c47f6c7def0b 100644
--- a/openmp/runtime/src/kmp_str.h
+++ b/openmp/runtime/src/kmp_str.h
@@ -13,6 +13,7 @@
 #ifndef KMP_STR_H
 #define KMP_STR_H
 
+#include <limits.h>
 #include <stdarg.h>
 #include <string.h>
 
@@ -112,7 +113,7 @@ int __kmp_str_match_true(char const *data);
 void __kmp_str_replace(char *str, char search_for, char replace_with);
 void __kmp_str_split(char *str, char delim, char **head, char **tail);
 char *__kmp_str_token(char *str, char const *delim, char **buf);
-int __kmp_basic_str_to_int(char const *str);
+int __kmp_basic_str_to_int(char const *str, int maxlen = INT_MAX);
 int __kmp_str_to_int(char const *str, char sentinel);
 
 void __kmp_str_to_size(char const *str, size_t *out, size_t dfactor,
diff --git a/openmp/runtime/unittests/ADT/CMakeLists.txt b/openmp/runtime/unittests/ADT/CMakeLists.txt
new file mode 100644
index 0000000000000..a29e70afe6df6
--- /dev/null
+++ b/openmp/runtime/unittests/ADT/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_openmp_unittest(ADTTests
+  TestStringRef.cpp
+)
+
diff --git a/openmp/runtime/unittests/ADT/TestStringRef.cpp b/openmp/runtime/unittests/ADT/TestStringRef.cpp
new file mode 100644
index 0000000000000..34790101dda34
--- /dev/null
+++ b/openmp/runtime/unittests/ADT/TestStringRef.cpp
@@ -0,0 +1,567 @@
+//===- TestStringRef.cpp - Tests for kmp_str_ref class -------------------===//
+//
+// 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 "kmp_adt.h"
+#include "kmp.h"
+#include "gtest/gtest.h"
+#include <cstring>
+
+namespace {
+
+// Helper to compare kmp_str_ref content with a C string
+static bool equals(const kmp_str_ref &s, const char *expected) {
+  size_t expected_len = strlen(expected);
+  if (s.length() != expected_len)
+    return false;
+  return memcmp(s.begin(), expected, expected_len) == 0;
+}
+
+//===----------------------------------------------------------------------===//
+// Construction and Basic Properties
+//===----------------------------------------------------------------------===//
+
+TEST(kmp_str_ref_test, ConstructFromCString) {
+  kmp_str_ref s("Hello");
+  EXPECT_EQ(s.length(), 5u);
+  EXPECT_TRUE(equals(s, "Hello"));
+}
+
+TEST(kmp_str_ref_test, ConstructFromCStringWithLength) {
+  kmp_str_ref s("Hello World", 5);
+  EXPECT_EQ(s.length(), 5u);
+  EXPECT_TRUE(equals(s, "Hello"));
+}
+
+TEST(kmp_str_ref_test, ConstructEmpty) {
+  kmp_str_ref s("");
+  EXPECT_EQ(s.length(), 0u);
+  EXPECT_TRUE(s.empty());
+}
+
+TEST(kmp_str_ref_test, Length) {
+  EXPECT_EQ(kmp_str_ref("").length(), 0u);
+  EXPECT_EQ(kmp_str_ref("a").length(), 1u);
+  EXPECT_EQ(kmp_str_ref("hello").length(), 5u);
+  EXPECT_EQ(kmp_str_ref("hello world").length(), 11u);
+}
+
+//===----------------------------------------------------------------------===//
+// empty
+//===----------------------------------------------------------------------===//
+
+TEST(kmp_str_ref_test, EmptyString) {
+  kmp_str_ref s("");
+  EXPECT_TRUE(s.empty());
+}
+
+TEST(kmp_str_ref_test, NonEmptyString) {
+  kmp_str_ref s("hello");
+  EXPECT_FALSE(s.empty());
+}
+
+TEST(kmp_str_ref_test, EmptyAfterConsumeFront) {
+  kmp_str_ref s("hello");
+  EXPECT_FALSE(s.empty());
+
+  s.consume_front("hello");
+
+  EXPECT_TRUE(s.empty());
+  EXPECT_EQ(s.length(), 0u);
+}
+
+TEST(kmp_str_ref_test, EmptyAfterDropFront) {
+  kmp_str_ref s("abc");
+  EXPECT_FALSE(s.empty());
+
+  s.drop_front(3);
+
+  EXPECT_TRUE(s.empty());
+  EXPECT_EQ(s.length(), 0u);
+}
+
+TEST(kmp_str_ref_test, EmptyAfterDropWhile) {
+  kmp_str_ref s("12345");
+  EXPECT_FALSE(s.empty());
+
+  s.drop_while([](char c) {
+    return static_cast<bool>(isdigit(static_cast<unsigned char>(c)));
+  });
+
+  EXPECT_TRUE(s.empty());
+  EXPECT_EQ(s.length(), 0u);
+}
+
+TEST(kmp_str_ref_test, EmptyAfterConsumeInteger) {
+  kmp_str_ref s("42");
+  int value = 0;
+  EXPECT_FALSE(s.empty());
+
+  s.consume_integer(value);
+
+  EXPECT_TRUE(s.empty());
+  EXPECT_EQ(s.length(), 0u);
+  EXPECT_EQ(value, 42);
+}
+
+TEST(kmp_str_ref_test, NotEmptyAfterPartialConsume) {
+  kmp_str_ref s("123abc");
+  int value = 0;
+
+  s.consume_integer(value);
+
+  EXPECT_FALSE(s.empty());
+  EXPECT_EQ(s.length(), 3u);
+  EXPECT_TRUE(equals(s, "abc"));
+}
+
+//===----------------------------------------------------------------------===//
+// Iterators
+//===----------------------------------------------------------------------===//
+
+TEST(kmp_str_ref_test, BeginEnd) {
+  kmp_str_ref s("Hello");
+  EXPECT_EQ(s.end() - s.begin(), 5);
+  EXPECT_EQ(*s.begin(), 'H');
+}
+
+TEST(kmp_str_ref_test, RangeBasedFor) {
+  kmp_str_ref s("abc");
+  std::string result;
+  for (char c : s) {
+    result += c;
+  }
+  EXPECT_EQ(result, "abc");
+}
+
+//===----------------------------------------------------------------------===//
+// Assignment
+//===----------------------------------------------------------------------===//
+
+TEST(kmp_str_ref_test, Assignment) {
+  kmp_str_ref s1("First");
+  kmp_str_ref s2("Second");
+
+  s1 = s2;
+
+  EXPECT_TRUE(equals(s1, "Second"));
+  EXPECT_EQ(s1.length(), 6u);
+}
+
+TEST(kmp_str_ref_test, SelfAssignment) {
+  kmp_str_ref s("Test");
+  kmp_str_ref &s_ref = s;
+  s = s_ref; // Avoid self-assignment warning
+  EXPECT_TRUE(equals(s, "Test"));
+  EXPECT_EQ(s.length(), 4u);
+}
+
+//===----------------------------------------------------------------------===//
+// consume_front
+//===----------------------------------------------------------------------===//
+
+TEST(kmp_str_ref_test, ConsumeFrontSuccess) {
+  kmp_str_ref s("Hello World");
+
+  EXPECT_TRUE(s.consume_front("Hello"));
+  EXPECT_EQ(s.length(), 6u);
+  EXPECT_TRUE(equals(s, " World"));
+}
+
+TEST(kmp_str_ref_test, ConsumeFrontFailure) {
+  kmp_str_ref s("Hello World");
+
+  EXPECT_FALSE(s.consume_front("World"));
+  EXPECT_EQ(s.length(), 11u);
+  EXPECT_TRUE(equals(s, "Hello World"));
+}
+
+TEST(kmp_str_ref_test, ConsumeFrontEmpty) {
+  kmp_str_ref s("Hello");
+
+  EXPECT_TRUE(s.consume_front(""));
+  EXPECT_EQ(s.length(), 5u);
+}
+
+TEST(kmp_str_ref_test, ConsumeFrontTooLong) {
+  kmp_str_ref s("Hi");
+
+  EXPECT_FALSE(s.consume_front("Hello"));
+  EXPECT_EQ(s.length(), 2u);
+}
+
+TEST(kmp_str_ref_test, ConsumeFrontExact) {
+  kmp_str_ref s("Hello");
+
+  EXPECT_TRUE(s.consume_front("Hello"));
+  EXPECT_EQ(s.length(), 0u);
+}
+
+TEST(kmp_str_ref_test, ConsumeFrontMultiple) {
+  kmp_str_ref s("prefix:middle:suffix");
+
+  EXPECT_TRUE(s.consume_front("prefix"));
+  EXPECT_TRUE(s.consume_front(":"));
+  EXPECT_TRUE(s.consume_front("middle"));
+  EXPECT_TRUE(s.consume_front(":"));
+  EXPECT_TRUE(equals(s, "suffix"));
+}
+
+//===----------------------------------------------------------------------===//
+// consume_integer
+//===----------------------------------------------------------------------===//
+
+TEST(kmp_str_ref_test, ConsumeIntegerSimple) {
+  kmp_str_ref s("42");
+  int value = 0;
+
+  EXPECT_TRUE(s.consume_integer(value));
+  EXPECT_EQ(value, 42);
+  EXPECT_EQ(s.length(), 0u);
+}
+
+TEST(kmp_str_ref_test, ConsumeIntegerWithTrailing) {
+  kmp_str_ref s("123abc");
+  int value = 0;
+
+  EXPECT_TRUE(s.consume_integer(value));
+  EXPECT_EQ(value, 123);
+  EXPECT_TRUE(equals(s, "abc"));
+}
+
+TEST(kmp_str_ref_test, ConsumeIntegerZero) {
+  kmp_str_ref s("0");
+  int value = -1;
+
+  // allow_zero = true by default
+  EXPECT_TRUE(s.consume_integer(value));
+  EXPECT_EQ(value, 0);
+  EXPECT_EQ(s.length(), 0u);
+}
+
+TEST(kmp_str_ref_test, ConsumeIntegerZeroNotAllowed) {
+  kmp_str_ref s("0rest");
+  int value = -1;
+
+  EXPECT_FALSE(s.consume_integer(value, /*allow_zero=*/false));
+  // State should be restored on failure
+  EXPECT_TRUE(equals(s, "0rest"));
+}
+
+TEST(kmp_str_ref_test, ConsumeIntegerNoDigits) {
+  kmp_str_ref s("abc");
+  int value = -1;
+
+  // No digits to consume, should fail
+  EXPECT_FALSE(s.consume_integer(value));
+  // String should be unchanged
+  EXPECT_TRUE(equals(s, "abc"));
+}
+
+TEST(kmp_str_ref_test, ConsumeIntegerEmpty) {
+  kmp_str_ref s("");
+  int value = -1;
+
+  // Empty string has no digits, should fail
+  EXPECT_FALSE(s.consume_integer(value));
+}
+
+TEST(kmp_str_ref_test, ConsumeIntegerLeadingZero) {
+  kmp_str_ref s("007");
+  int value = -1;
+
+  EXPECT_TRUE(s.consume_integer(value));
+  EXPECT_EQ(value, 7);
+  EXPECT_EQ(s.length(), 0u);
+}
+
+TEST(kmp_str_ref_test, ConsumeIntegerNegativeAllowed) {
+  kmp_str_ref s("-42rest");
+  int value = 0;
+
+  EXPECT_TRUE(s.consume_integer(value, true, true));
+  EXPECT_EQ(value, -42);
+  EXPECT_TRUE(equals(s, "rest"));
+}
+
+TEST(kmp_str_ref_test, ConsumeIntegerNegativeNotAllowed) {
+  kmp_str_ref s("-42");
+  int value = 0;
+
+  EXPECT_FALSE(s.consume_integer(value, true, false));
+  // State should be restored on failure
+  EXPECT_TRUE(equals(s, "-42"));
+}
+
+TEST(kmp_str_ref_test, ConsumeIntegerMultipleDigits) {
+  kmp_str_ref s("1234567890");
+  int value = 0;
+
+  EXPECT_TRUE(s.consume_integer(value));
+  EXPECT_EQ(value, 1234567890);
+}
+
+//===----------------------------------------------------------------------===//
+// copy
+//===----------------------------------------------------------------------===//
+
+TEST(kmp_str_ref_test, Copy) {
+  kmp_str_ref s("Hello");
+  char *copied = s.copy();
+
+  EXPECT_NE(copied, nullptr);
+  EXPECT_STREQ(copied, "Hello");
+  EXPECT_NE(copied, s.begin()); // Different pointer
+
+  KMP_INTERNAL_FREE(copied);
+}
+
+TEST(kmp_str_ref_test, CopyEmpty) {
+  kmp_str_ref s("");
+  char *copied = s.copy();
+
+  EXPECT_NE(copied, nullptr);
+  EXPECT_STREQ(copied, "");
+
+  KMP_INTERNAL_FREE(copied);
+}
+
+TEST(kmp_str_ref_test, CopySubstring) {
+  // Test copying a substring that doesn't have a null terminator at len
+  kmp_str_ref full("device-0)rest");
+  kmp_str_ref sub = full.take_while([](char c) { return c != ')'; });
+
+  EXPECT_EQ(sub.length(), 8u); // "device-0"
+
+  char *copied = sub.copy();
+
+  EXPECT_NE(copied, nullptr);
+  EXPECT_STREQ(copied, "device-0"); // Should NOT include ")"
+  EXPECT_EQ(strlen(copied), 8u);
+
+  KMP_INTERNAL_FREE(copied);
+}
+
+//===----------------------------------------------------------------------===//
+// drop_front
+//===----------------------------------------------------------------------===//
+
+TEST(kmp_str_ref_test, DropFront) {
+  kmp_str_ref s("Hello World");
+
+  s.drop_front(6);
+
+  EXPECT_EQ(s.length(), 5u);
+  EXPECT_TRUE(equals(s, "World"));
+}
+
+TEST(kmp_str_ref_test, DropFrontZero) {
+  kmp_str_ref s("Hello");
+
+  s.drop_front(0);
+
+  EXPECT_EQ(s.length(), 5u);
+  EXPECT_TRUE(equals(s, "Hello"));
+}
+
+TEST(kmp_str_ref_test, DropFrontAll) {
+  kmp_str_ref s("Hello");
+
+  s.drop_front(5);
+
+  EXPECT_EQ(s.length(), 0u);
+}
+
+TEST(kmp_str_ref_test, DropFrontMoreThanLength) {
+  kmp_str_ref s("Hi");
+
+  s.drop_front(100);
+
+  EXPECT_EQ(s.length(), 0u);
+}
+
+//===----------------------------------------------------------------------===//
+// drop_while
+//===----------------------------------------------------------------------===//
+
+TEST(kmp_str_ref_test, DropWhileDigits) {
+  kmp_str_ref s("123abc");
+
+  s.drop_while([](char c) {
+    return static_cast<bool>(isdigit(static_cast<unsigned char>(c)));
+  });
+
+  EXPECT_TRUE(equals(s, "abc"));
+}
+
+TEST(kmp_str_ref_test, DropWhileSpaces) {
+  kmp_str_ref s("   hello");
+
+  s.drop_while([](char c) { return c == ' '; });
+
+  EXPECT_TRUE(equals(s, "hello"));
+}
+
+TEST(kmp_str_ref_test, DropWhileNone) {
+  kmp_str_ref s("hello");
+
+  s.drop_while([](char c) { return c == ' '; });
+
+  EXPECT_TRUE(equals(s, "hello"));
+}
+
+TEST(kmp_str_ref_test, DropWhileAll) {
+  kmp_str_ref s("12345");
+
+  s.drop_while([](char c) {
+    return static_cast<bool>(isdigit(static_cast<unsigned char>(c)));
+  });
+
+  EXPECT_EQ(s.length(), 0u);
+}
+
+//===----------------------------------------------------------------------===//
+// skip_space
+//===----------------------------------------------------------------------===//
+
+TEST(kmp_str_ref_test, SkipSpace) {
+  kmp_str_ref s("   hello");
+
+  s.skip_space();
+
+  EXPECT_TRUE(equals(s, "hello"));
+}
+
+TEST(kmp_str_ref_test, SkipSpaceNoSpaces) {
+  kmp_str_ref s("hello");
+
+  s.skip_space();
+
+  EXPECT_TRUE(equals(s, "hello"));
+}
+
+TEST(kmp_str_ref_test, SkipSpaceAllSpaces) {
+  kmp_str_ref s("     ");
+
+  s.skip_space();
+
+  EXPECT_EQ(s.length(), 0u);
+}
+
+TEST(kmp_str_ref_test, SkipSpaceOnlyLeading) {
+  kmp_str_ref s("  hello world  ");
+
+  s.skip_space();
+
+  EXPECT_TRUE(equals(s, "hello world  "));
+}
+
+TEST(kmp_str_ref_test, SkipSpaceWithTabs) {
+  kmp_str_ref s("\t\n  hello");
+
+  s.skip_space();
+
+  EXPECT_TRUE(equals(s, "hello"));
+}
+
+//===----------------------------------------------------------------------===//
+// take_while
+//===----------------------------------------------------------------------===//
+
+TEST(kmp_str_ref_test, TakeWhileDigits) {
+  kmp_str_ref s("123abc");
+
+  kmp_str_ref digits = s.take_while([](char c) {
+    return static_cast<bool>(isdigit(static_cast<unsigned char>(c)));
+  });
+
+  EXPECT_EQ(digits.length(), 3u);
+  EXPECT_TRUE(equals(digits, "123"));
+  // Original unchanged
+  EXPECT_EQ(s.length(), 6u);
+}
+
+TEST(kmp_str_ref_test, TakeWhileAlpha) {
+  kmp_str_ref s("hello123");
+
+  kmp_str_ref alpha = s.take_while([](char c) {
+    return static_cast<bool>(isalpha(static_cast<unsigned char>(c)));
+  });
+
+  EXPECT_EQ(alpha.length(), 5u);
+  EXPECT_TRUE(equals(alpha, "hello"));
+}
+
+TEST(kmp_str_ref_test, TakeWhileNone) {
+  kmp_str_ref s("123abc");
+
+  kmp_str_ref result = s.take_while([](char c) {
+    return static_cast<bool>(isalpha(static_cast<unsigned char>(c)));
+  });
+
+  EXPECT_EQ(result.length(), 0u);
+}
+
+TEST(kmp_str_ref_test, TakeWhileAll) {
+  kmp_str_ref s("hello");
+
+  kmp_str_ref result = s.take_while([](char c) {
+    return static_cast<bool>(isalpha(static_cast<unsigned char>(c)));
+  });
+
+  EXPECT_EQ(result.length(), 5u);
+  EXPECT_TRUE(equals(result, "hello"));
+}
+
+//===----------------------------------------------------------------------===//
+// Integration / Complex Scenarios
+//===----------------------------------------------------------------------===//
+
+TEST(kmp_str_ref_test, ParseKeyValuePair) {
+  kmp_str_ref s("key=value");
+
+  kmp_str_ref key = s.take_while([](char c) { return c != '='; });
+  s.drop_front(key.length());
+  s.consume_front("=");
+
+  EXPECT_EQ(key.length(), 3u);
+  EXPECT_TRUE(equals(key, "key"));
+  EXPECT_TRUE(equals(s, "value"));
+}
+
+TEST(kmp_str_ref_test, ParseCommaSeparated) {
+  kmp_str_ref s("1,2,3");
+  int values[3] = {0, 0, 0};
+  int count = 0;
+
+  while (s.length() > 0 && count < 3) {
+    s.consume_integer(values[count++]);
+    s.consume_front(",");
+  }
+
+  EXPECT_EQ(count, 3);
+  EXPECT_EQ(values[0], 1);
+  EXPECT_EQ(values[1], 2);
+  EXPECT_EQ(values[2], 3);
+}
+
+TEST(kmp_str_ref_test, ParseWithWhitespace) {
+  kmp_str_ref s("  hello  world  ");
+
+  s.skip_space();
+  kmp_str_ref word1 = s.take_while([](char c) { return c != ' '; });
+  s.drop_front(word1.length());
+  s.skip_space();
+  kmp_str_ref word2 = s.take_while([](char c) { return c != ' '; });
+
+  EXPECT_EQ(word1.length(), 5u);
+  EXPECT_TRUE(equals(word1, "hello"));
+  EXPECT_EQ(word2.length(), 5u);
+  EXPECT_TRUE(equals(word2, "world"));
+}
+
+} // namespace
diff --git a/openmp/runtime/unittests/CMakeLists.txt b/openmp/runtime/unittests/CMakeLists.txt
index 9e00456fd8fb4..dada4f9fc65fd 100644
--- a/openmp/runtime/unittests/CMakeLists.txt
+++ b/openmp/runtime/unittests/CMakeLists.txt
@@ -86,4 +86,5 @@ add_openmp_testsuite(
   DEPENDS OpenMPUnitTests
 )
 
+add_subdirectory(ADT)
 add_subdirectory(String)
diff --git a/openmp/runtime/unittests/String/TestKmpStr.cpp b/openmp/runtime/unittests/String/TestKmpStr.cpp
index 87497b93c7538..5436ef1058c74 100644
--- a/openmp/runtime/unittests/String/TestKmpStr.cpp
+++ b/openmp/runtime/unittests/String/TestKmpStr.cpp
@@ -111,6 +111,33 @@ TEST(KmpStrTest, BasicStrToIntInvalid) {
   EXPECT_EQ(__kmp_basic_str_to_int("7.5"), 7);
 }
 
+// Test basic string to int conversion with maxlen parameter
+TEST(KmpStrTest, BasicStrToIntMaxLen) {
+  // maxlen limits how many characters are parsed
+  EXPECT_EQ(__kmp_basic_str_to_int("12345", 3), 123);
+  EXPECT_EQ(__kmp_basic_str_to_int("12345", 1), 1);
+  EXPECT_EQ(__kmp_basic_str_to_int("12345", 5), 12345);
+
+  // maxlen larger than string length parses entire string
+  EXPECT_EQ(__kmp_basic_str_to_int("42", 10), 42);
+  EXPECT_EQ(__kmp_basic_str_to_int("123", 100), 123);
+
+  // maxlen of 0 returns 0 (no characters parsed)
+  EXPECT_EQ(__kmp_basic_str_to_int("12345", 0), 0);
+
+  // maxlen with mixed content: stops at maxlen or non-digit, whichever first
+  EXPECT_EQ(__kmp_basic_str_to_int("123abc", 2), 12);
+  EXPECT_EQ(__kmp_basic_str_to_int("123abc", 5),
+            123); // stops at 'a' before maxlen
+
+  // maxlen with leading zeros
+  EXPECT_EQ(__kmp_basic_str_to_int("007", 2), 0);
+  EXPECT_EQ(__kmp_basic_str_to_int("007", 3), 7);
+
+  // Default maxlen (INT_MAX) behaves like before
+  EXPECT_EQ(__kmp_basic_str_to_int("999"), 999);
+}
+
 // Test string match
 TEST(KmpStrTest, StrMatch) {
   const char *data = "Hello World";

>From 1e3c35165316d5fa2221ef72b700644df51d908d Mon Sep 17 00:00:00 2001
From: Robert Imschweiler <robert.imschweiler at amd.com>
Date: Wed, 28 Jan 2026 09:11:53 -0600
Subject: [PATCH 2/2] use string_view

---
 openmp/runtime/src/kmp_adt.h                  | 45 ++++++++-----------
 .../runtime/unittests/ADT/TestStringRef.cpp   |  4 +-
 2 files changed, 21 insertions(+), 28 deletions(-)

diff --git a/openmp/runtime/src/kmp_adt.h b/openmp/runtime/src/kmp_adt.h
index 35069d377e40e..120a067097978 100644
--- a/openmp/runtime/src/kmp_adt.h
+++ b/openmp/runtime/src/kmp_adt.h
@@ -17,21 +17,17 @@
 #include <cctype>
 #include <cstddef>
 #include <cstring>
+#include <string_view>
 
 #include "kmp.h"
 
 /// kmp_str_ref is a non-owning string class (similar to llvm::StringRef).
 class kmp_str_ref final {
-  const char *data;
-  size_t len;
+  std::string_view sv;
 
 public:
-  kmp_str_ref(const char *data) : data(data), len(data ? strlen(data) : 0) {
-    assert(data && "kmp_str_ref cannot be constructed from nullptr");
-  }
-  kmp_str_ref(const char *data, size_t len) : data(data), len(len) {
-    assert(data && "kmp_str_ref cannot be constructed from nullptr");
-  }
+  kmp_str_ref(const char *str) : sv(str) {}
+  kmp_str_ref(std::string_view sv) : sv(sv) {}
 
   kmp_str_ref(const kmp_str_ref &other) = default;
   kmp_str_ref &operator=(const kmp_str_ref &other) = default;
@@ -39,12 +35,11 @@ class kmp_str_ref final {
   // Check if the string starts with the given prefix and remove it from the
   // string afterwards.
   bool consume_front(kmp_str_ref prefix) {
-    if (len < prefix.len)
+    if (length() < prefix.length())
       return false;
-    if (memcmp(data, prefix.data, prefix.len) != 0)
+    if (sv.compare(0, prefix.length(), prefix.sv) != 0)
       return false;
-    data += prefix.len;
-    len -= prefix.len;
+    drop_front(prefix.length());
     return true;
   }
 
@@ -67,7 +62,7 @@ class kmp_str_ref final {
       return false;
     }
     assert(num_digits <= INT_MAX);
-    value = __kmp_basic_str_to_int(data, static_cast<int>(num_digits));
+    value = __kmp_basic_str_to_int(sv.data(), static_cast<int>(num_digits));
     if (value == INT_MAX) {
       *this = orig;
       return false;
@@ -85,10 +80,10 @@ class kmp_str_ref final {
   // Get an own duplicate of the string.
   // Must be freed with KMP_INTERNAL_FREE().
   char *copy() const {
-    char *copy_str = static_cast<char *>(KMP_INTERNAL_MALLOC(len + 1));
+    char *copy_str = static_cast<char *>(KMP_INTERNAL_MALLOC(length() + 1));
     assert(copy_str && "copy() failed to allocate memory");
-    memcpy(copy_str, data, len);
-    copy_str[len] = '\0';
+    memcpy(copy_str, sv.data(), length());
+    copy_str[length()] = '\0';
     return copy_str;
   }
 
@@ -96,17 +91,15 @@ class kmp_str_ref final {
   // true.
   size_t count_while(bool (*predicate)(char)) const {
     size_t i = 0;
-    while (i < len && predicate(data[i]))
+    while (i < length() && predicate(sv[i]))
       ++i;
     return i;
   }
 
   // Drop the first n characters from the string.
+  // (Limit n to the length of the string.)
   void drop_front(size_t n) {
-    if (n > len)
-      n = len;
-    data += n;
-    len -= n;
+    sv.remove_prefix(std::min(n, length()));
   }
 
   // Drop characters from the string while the predicate returns true.
@@ -115,10 +108,10 @@ class kmp_str_ref final {
   }
 
   // Check if the string is empty.
-  bool empty() const { return len == 0; }
+  bool empty() const { return sv.empty(); }
 
   // Get the length of the string.
-  size_t length() const { return len; }
+  size_t length() const { return sv.length(); }
 
   // Drop space from the start of the string.
   void skip_space() {
@@ -130,12 +123,12 @@ class kmp_str_ref final {
   // Construct a new string with the longest prefix of the original string that
   // satisfies the predicate. Doesn't modify the original string.
   kmp_str_ref take_while(bool (*predicate)(char)) const {
-    return kmp_str_ref(data, count_while(predicate));
+    return kmp_str_ref(sv.substr(0, count_while(predicate)));
   }
 
   // Iterator support (raw pointers work as iterators for contiguous storage)
-  const char *begin() const { return data; }
-  const char *end() const { return data + len; }
+  const char *begin() const { return sv.data(); }
+  const char *end() const { return sv.data() + length(); }
 };
 
 #endif // __KMP_ADT_H__
diff --git a/openmp/runtime/unittests/ADT/TestStringRef.cpp b/openmp/runtime/unittests/ADT/TestStringRef.cpp
index 34790101dda34..cb3e9f8466e38 100644
--- a/openmp/runtime/unittests/ADT/TestStringRef.cpp
+++ b/openmp/runtime/unittests/ADT/TestStringRef.cpp
@@ -31,8 +31,8 @@ TEST(kmp_str_ref_test, ConstructFromCString) {
   EXPECT_TRUE(equals(s, "Hello"));
 }
 
-TEST(kmp_str_ref_test, ConstructFromCStringWithLength) {
-  kmp_str_ref s("Hello World", 5);
+TEST(kmp_str_ref_test, ConstructFromStringView) {
+  kmp_str_ref s(std::string_view("Hello World", 5));
   EXPECT_EQ(s.length(), 5u);
   EXPECT_TRUE(equals(s, "Hello"));
 }



More information about the Openmp-commits mailing list