[llvm] [orc-rt] Add ConnectionSpec, a connection-string parser (PR #225304)

Lang Hames via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 21 22:56:32 PDT 2026


https://github.com/lhames created https://github.com/llvm/llvm-project/pull/225304

Parses the string a process is given to describe the connection it should establish with its peer:

  <transport>[:<action>]=<descriptor>

Checking is punctuation only: the transport and action are opaque tokens and the descriptor's syntax belongs to the transport, so a spec parses without knowing which transports this process was built with.

The grammar is LLVM's (#225075), so the same vocabulary reaches an orc-rt executor and an LLVM controller. A registry mapping transports to the connectors that implement them will be added in a follow-up commit.

>From d3dc1d185c711d24629aee749099cb5e40269bb8 Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at gmail.com>
Date: Tue, 22 Sep 2026 14:06:08 +1000
Subject: [PATCH] [orc-rt] Add ConnectionSpec, a connection-string parser

Parses the string a process is given to describe the connection it
should establish with its peer:

  <transport>[:<action>]=<descriptor>

Checking is punctuation only: the transport and action are opaque tokens
and the descriptor's syntax belongs to the transport, so a spec parses
without knowing which transports this process was built with.

The grammar is LLVM's (#225075), so the same vocabulary reaches an
orc-rt executor and an LLVM controller. A registry mapping transports to
the connectors that implement them will be added in a follow-up commit.
---
 .../include/orc-rt/bedrock/ConnectionSpec.h   | 96 +++++++++++++++++++
 orc-rt/lib/bedrock/CMakeLists.txt             |  1 +
 orc-rt/lib/bedrock/ConnectionSpec.cpp         | 50 ++++++++++
 orc-rt/test/unit/CMakeLists.txt               |  1 +
 .../test/unit/bedrock/ConnectionSpecTest.cpp  | 83 ++++++++++++++++
 5 files changed, 231 insertions(+)
 create mode 100644 orc-rt/include/orc-rt/bedrock/ConnectionSpec.h
 create mode 100644 orc-rt/lib/bedrock/ConnectionSpec.cpp
 create mode 100644 orc-rt/test/unit/bedrock/ConnectionSpecTest.cpp

diff --git a/orc-rt/include/orc-rt/bedrock/ConnectionSpec.h b/orc-rt/include/orc-rt/bedrock/ConnectionSpec.h
new file mode 100644
index 0000000000000..a5351ea18d63c
--- /dev/null
+++ b/orc-rt/include/orc-rt/bedrock/ConnectionSpec.h
@@ -0,0 +1,96 @@
+//===- ConnectionSpec.h - Connection spec parsing ---------------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// A ConnectionSpec describes one connection a process should establish with
+// its peer, e.g. "tcp:connect=localhost:20000" or "socket:adopt=3".
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef ORC_RT_BEDROCK_CONNECTIONSPEC_H
+#define ORC_RT_BEDROCK_CONNECTIONSPEC_H
+
+#include "orc-rt/support/Error.h"
+
+#include <string>
+#include <string_view>
+
+namespace orc_rt {
+
+/// The parsed form of a string describing one connection a process should
+/// establish with its peer:
+///
+///   <transport>[:<action>]=<descriptor>
+///
+///   transport   what kind of thing the descriptor names: "tcp" for a host:port
+///               endpoint, "socket" for a handle this process already holds.
+///   action      what this process does with it: "connect", "listen", or
+///               "adopt" for a handle it was handed.
+///   descriptor  the thing itself, in whatever syntax the transport defines.
+///
+/// E.g. "tcp:connect=localhost:20000", "tcp:listen=[::1]:0", "socket:adopt=3".
+///
+/// A spec says what the process reading it does, so the two ends of one
+/// connection carry different specs: an executor told
+/// "tcp:connect=<host>:<port>" pairs with a controller told "tcp:listen=:0".
+///
+/// Parsing checks punctuation only: transport and action are opaque tokens, the
+/// descriptor's syntax belongs to the transport, and all three fields are kept
+/// verbatim -- so a caller matching a name against a known set does so
+/// case-sensitively. Only the text before the first '=' is searched for the
+/// ':', which lets a descriptor hold ':' and '=' unescaped.
+class ConnectionSpec {
+public:
+  /// Parses Spec as <transport>[:<action>]=<descriptor>.
+  ///
+  /// Checks only the punctuation: that there is an '=', and that the names
+  /// either side of it are non-empty where required.
+  static Expected<ConnectionSpec> parse(std::string_view Spec) noexcept;
+
+  ConnectionSpec() = default;
+
+  /// Return true if this is an empty spec.
+  bool empty() const noexcept {
+    return Transport.empty() && Action.empty() && Descriptor.empty();
+  }
+
+  /// The transport name, e.g. "tcp". Never empty.
+  const std::string &transport() const noexcept { return Transport; }
+
+  /// The action name, e.g. "connect" or "adopt". May be empty.
+  const std::string &action() const noexcept { return Action; }
+
+  /// The thing the transport names, in whatever syntax that transport defines.
+  /// Opaque here: the parser neither splits nor validates it. May be empty.
+  const std::string &descriptor() const noexcept { return Descriptor; }
+
+  /// Rebuilds the original connection string, e.g. "tcp:connect=host:port".
+  std::string str() const noexcept {
+    std::string S = Transport;
+    if (!Action.empty()) {
+      S += ':';
+      S += Action;
+    }
+    S += '=';
+    S += Descriptor;
+    return S;
+  }
+
+private:
+  ConnectionSpec(std::string Transport, std::string Action,
+                 std::string Descriptor) noexcept
+      : Transport(std::move(Transport)), Action(std::move(Action)),
+        Descriptor(std::move(Descriptor)) {}
+
+  std::string Transport;
+  std::string Action;
+  std::string Descriptor;
+};
+
+} // namespace orc_rt
+
+#endif // ORC_RT_BEDROCK_CONNECTIONSPEC_H
diff --git a/orc-rt/lib/bedrock/CMakeLists.txt b/orc-rt/lib/bedrock/CMakeLists.txt
index 5dc584bca9659..99016c5866d41 100644
--- a/orc-rt/lib/bedrock/CMakeLists.txt
+++ b/orc-rt/lib/bedrock/CMakeLists.txt
@@ -1,6 +1,7 @@
 
 set(ORC_RT_BEDROCK_SOURCES
   BootstrapInfo.cpp
+  ConnectionSpec.cpp
   ExecutorProcessInfo.cpp
   GDBJITRegistrar.cpp
   InProcessControllerAccess.cpp
diff --git a/orc-rt/lib/bedrock/ConnectionSpec.cpp b/orc-rt/lib/bedrock/ConnectionSpec.cpp
new file mode 100644
index 0000000000000..0b517e62b6c8b
--- /dev/null
+++ b/orc-rt/lib/bedrock/ConnectionSpec.cpp
@@ -0,0 +1,50 @@
+//===- ConnectionSpec.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Connection spec parsing.
+//
+//===----------------------------------------------------------------------===//
+
+#include "orc-rt/bedrock/ConnectionSpec.h"
+
+#include "orc-rt-internal/support/StringExtras.h"
+
+namespace orc_rt {
+
+Expected<ConnectionSpec> ConnectionSpec::parse(std::string_view Spec) noexcept {
+  auto Fail = [&](std::string_view Msg) -> Error {
+    StringOutputStream OS;
+    OS << "in connection spec '" << Spec << "': " << Msg;
+    return make_error<StringError>(std::move(OS).str());
+  };
+
+  auto EqPos = Spec.find('=');
+  if (EqPos == std::string_view::npos)
+    return Fail("expected '<transport>[:<action>]=<descriptor>', but found "
+                "no '='");
+
+  auto Descriptor = Spec.substr(EqPos + 1);
+  auto LHS = Spec.substr(0, EqPos);
+  std::string_view Transport, Action;
+
+  if (auto ColonPos = LHS.find(':'); ColonPos != std::string_view::npos) {
+    Transport = LHS.substr(0, ColonPos);
+    Action = LHS.substr(ColonPos + 1);
+    if (Action.empty())
+      return Fail("empty action name");
+  } else
+    Transport = LHS;
+
+  if (Transport.empty())
+    return Fail("empty transport name");
+
+  return ConnectionSpec(std::string(Transport), std::string(Action),
+                        std::string(Descriptor));
+}
+
+} // namespace orc_rt
diff --git a/orc-rt/test/unit/CMakeLists.txt b/orc-rt/test/unit/CMakeLists.txt
index 41132cc786d10..208be6313036d 100644
--- a/orc-rt/test/unit/CMakeLists.txt
+++ b/orc-rt/test/unit/CMakeLists.txt
@@ -107,6 +107,7 @@ endif()
 
 add_orc_rt_unittest(BedrockTests
   bedrock/BootstrapInfoTest.cpp
+  bedrock/ConnectionSpecTest.cpp
   bedrock/ExecutorProcessInfoTest.cpp
   bedrock/InProcessControllerAccessTest.cpp
   bedrock/NativeDylibManagerTest.cpp
diff --git a/orc-rt/test/unit/bedrock/ConnectionSpecTest.cpp b/orc-rt/test/unit/bedrock/ConnectionSpecTest.cpp
new file mode 100644
index 0000000000000..7bea53ee7712e
--- /dev/null
+++ b/orc-rt/test/unit/bedrock/ConnectionSpecTest.cpp
@@ -0,0 +1,83 @@
+//===- ConnectionSpecTest.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 "orc-rt/bedrock/ConnectionSpec.h"
+#include "orc-rt/support/Error.h"
+#include "gtest/gtest.h"
+
+using namespace orc_rt;
+
+namespace {
+
+TEST(ConnectionSpecTest, TransportActionAndDescriptor) {
+  auto CS = ConnectionSpec::parse("tcp:connect=localhost:20000");
+  ASSERT_TRUE(!!CS);
+  EXPECT_EQ(CS->transport(), "tcp");
+  EXPECT_EQ(CS->action(), "connect");
+  EXPECT_EQ(CS->descriptor(), "localhost:20000");
+}
+
+TEST(ConnectionSpecTest, TransportActionAndMultiPartDescriptor) {
+  auto CS = ConnectionSpec::parse("pipe:adopt=3,4");
+  ASSERT_TRUE(!!CS);
+  EXPECT_EQ(CS->transport(), "pipe");
+  EXPECT_EQ(CS->action(), "adopt");
+  EXPECT_EQ(CS->descriptor(), "3,4");
+}
+
+TEST(ConnectionSpecTest, EmptyDescriptorIsAllowed) {
+  // Whether an empty descriptor means anything is the transport's business, so
+  // it isn't a syntax error here.
+  auto CS = ConnectionSpec::parse("socket:adopt=");
+  ASSERT_TRUE(!!CS);
+  EXPECT_EQ(CS->transport(), "socket");
+  EXPECT_EQ(CS->descriptor(), "");
+}
+
+TEST(ConnectionSpecTest, DescriptorKeepsColonsAndEquals) {
+  auto CS = ConnectionSpec::parse("tcp:listen=[::1]:0");
+  ASSERT_TRUE(!!CS);
+  EXPECT_EQ(CS->transport(), "tcp");
+  EXPECT_EQ(CS->action(), "listen");
+  EXPECT_EQ(CS->descriptor(), "[::1]:0");
+
+  // Only the first '=' is structural.
+  auto Odd = ConnectionSpec::parse("unix:listen=/tmp/a=b.sock");
+  ASSERT_TRUE(!!Odd);
+  EXPECT_EQ(Odd->descriptor(), "/tmp/a=b.sock");
+}
+
+TEST(ConnectionSpecTest, MissingEqualsFails) {
+  auto CS = ConnectionSpec::parse("tcp:connect");
+  EXPECT_FALSE(!!CS);
+  consumeError(CS.takeError());
+}
+
+TEST(ConnectionSpecTest, EmptyTransportFails) {
+  auto CS = ConnectionSpec::parse("=localhost:20000");
+  EXPECT_FALSE(!!CS);
+  consumeError(CS.takeError());
+
+  auto WithAction = ConnectionSpec::parse(":connect=localhost:20000");
+  EXPECT_FALSE(!!WithAction);
+  consumeError(WithAction.takeError());
+}
+
+TEST(ConnectionSpecTest, EmptyActionFails) {
+  auto CS = ConnectionSpec::parse("tcp:=localhost:20000");
+  EXPECT_FALSE(!!CS);
+  consumeError(CS.takeError());
+}
+
+TEST(ConnectionSpecTest, ErrorMessageQuotesTheSpec) {
+  auto CS = ConnectionSpec::parse("tcp:connect");
+  ASSERT_FALSE(!!CS);
+  EXPECT_NE(toString(CS.takeError()).find("'tcp:connect'"), std::string::npos);
+}
+
+} // namespace



More information about the llvm-commits mailing list