[llvm] [orc-rt] Add MsgHeader, the SimpleRemote stream header (PR #225336)

Lang Hames via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 22 02:14:11 PDT 2026


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

A SimpleRemote message carries an opcode, a sequence number, a tag and a payload. MsgHeader is how a byte-stream transport puts those on the wire: four little-endian uint64 fields, size first, followed by the payload.

Adding MsgHeader to SimpleRemoteCA makes it easy for stream-based transports to reuse.

>From 8531878087e27460a2eaf28174d41e99030889f1 Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at gmail.com>
Date: Tue, 22 Sep 2026 18:23:19 +1000
Subject: [PATCH] [orc-rt] Add MsgHeader, the SimpleRemote stream header

A SimpleRemote message carries an opcode, a sequence number, a tag and
a payload. MsgHeader is how a byte-stream transport puts those on the
wire: four little-endian uint64 fields, size first, followed by the
payload.

Adding MsgHeader to SimpleRemoteCA makes it easy for stream-based
transports to reuse.
---
 .../orc-rt/bedrock/sps/SimpleRemoteCA.h       | 31 ++++++++++++++++++
 orc-rt/lib/bedrock/sps/SimpleRemoteCA.cpp     | 29 +++++++++++++++++
 .../unit/bedrock/sps/SimpleRemoteCATest.cpp   | 32 +++++++++++++++++++
 3 files changed, 92 insertions(+)

diff --git a/orc-rt/include/orc-rt/bedrock/sps/SimpleRemoteCA.h b/orc-rt/include/orc-rt/bedrock/sps/SimpleRemoteCA.h
index 5ffe601d5365d..b8f1bf9d81f4f 100644
--- a/orc-rt/include/orc-rt/bedrock/sps/SimpleRemoteCA.h
+++ b/orc-rt/include/orc-rt/bedrock/sps/SimpleRemoteCA.h
@@ -18,6 +18,7 @@
 #include "orc-rt/support/Error.h"
 #include "orc-rt/support/WrapperFunction.h"
 
+#include <cstddef>
 #include <cstdint>
 #include <unordered_map>
 #include <utility>
@@ -58,6 +59,36 @@ class SimpleRemoteCA : public Session::ControllerAccess {
     LastResultKind = OutOfBandError
   };
 
+  /// How a message header goes onto a byte stream: four little-endian uint64
+  /// fields, followed by the payload.
+  ///
+  /// The sole authority for that layout, so that an encoder and a reader cannot
+  /// disagree about it. Shared by the stream transports rather than owned by
+  /// one of them -- a socket, a pipe and a Windows socket all frame this way,
+  /// and the layout matches LLVM's FDMsgHeader.
+  ///
+  /// Not universal, though. MsgSize is here only because a stream has no
+  /// message boundaries of its own, so a message-oriented transport -- XPC,
+  /// say, where these would be dictionary keys -- carries the same opcode,
+  /// sequence number and tag with neither this encoding nor a length.
+  struct MsgHeader {
+    static constexpr size_t Size = 4 * sizeof(uint64_t);
+
+    /// A decoded header. MsgSize counts the header as well as the payload.
+    /// Tag is the raw wire value: a handler tag for a call, a ResultKind for a
+    /// result, zero otherwise.
+    struct Fields {
+      uint64_t MsgSize = 0;
+      uint64_t OpC = 0;
+      uint64_t SeqNo = 0;
+      uint64_t Tag = 0;
+    };
+
+    static void encode(char *Buf, Opcode Op, uint64_t SeqNo, uint64_t Tag,
+                       size_t PayloadSize);
+    static Fields decode(const char *Buf);
+  };
+
   /// The name of Op, for logging.
   static const char *getOpcodeName(Opcode Op) noexcept;
 
diff --git a/orc-rt/lib/bedrock/sps/SimpleRemoteCA.cpp b/orc-rt/lib/bedrock/sps/SimpleRemoteCA.cpp
index 94d96cb069840..106ebc89c43a1 100644
--- a/orc-rt/lib/bedrock/sps/SimpleRemoteCA.cpp
+++ b/orc-rt/lib/bedrock/sps/SimpleRemoteCA.cpp
@@ -12,6 +12,8 @@
 
 #include "orc-rt/bedrock/sps/SimpleRemoteCA.h"
 
+#include "orc-rt-internal/support/Endian.h"
+
 #include "orc-rt/support/Compiler.h"
 #include "orc-rt/support/ExecutorAddress.h"
 #include "orc-rt/support/iterator_range.h"
@@ -25,6 +27,13 @@ namespace orc_rt {
 
 namespace {
 
+// Header field offsets. Wire values, shared with LLVM's FDMsgHeader: do not
+// reorder.
+constexpr size_t MsgSizeOffset = 0;
+constexpr size_t OpCOffset = 8;
+constexpr size_t SeqNoOffset = 16;
+constexpr size_t TagAddrOffset = 24;
+
 /// Converts a call message's tag to the wrapper function it names.
 ///
 /// Tags travel as uint64_t whatever the pointer width at either end, so a
@@ -42,6 +51,26 @@ Expected<orc_rt_WrapperFunction> tagToWrapperFunction(uint64_t Tag) {
 
 } // namespace
 
+void SimpleRemoteCA::MsgHeader::encode(char *Buf, Opcode Op, uint64_t SeqNo,
+                                       uint64_t Tag, size_t PayloadSize) {
+  endian_write<uint64_t>(Buf + MsgSizeOffset, Size + PayloadSize,
+                         endian::little);
+  endian_write<uint64_t>(Buf + OpCOffset, static_cast<uint64_t>(Op),
+                         endian::little);
+  endian_write<uint64_t>(Buf + SeqNoOffset, SeqNo, endian::little);
+  endian_write<uint64_t>(Buf + TagAddrOffset, Tag, endian::little);
+}
+
+SimpleRemoteCA::MsgHeader::Fields
+SimpleRemoteCA::MsgHeader::decode(const char *Buf) {
+  Fields F;
+  F.MsgSize = endian_read<uint64_t>(Buf + MsgSizeOffset, endian::little);
+  F.OpC = endian_read<uint64_t>(Buf + OpCOffset, endian::little);
+  F.SeqNo = endian_read<uint64_t>(Buf + SeqNoOffset, endian::little);
+  F.Tag = endian_read<uint64_t>(Buf + TagAddrOffset, endian::little);
+  return F;
+}
+
 const char *SimpleRemoteCA::getOpcodeName(Opcode Op) noexcept {
   switch (Op) {
   case Opcode::Setup:
diff --git a/orc-rt/test/unit/bedrock/sps/SimpleRemoteCATest.cpp b/orc-rt/test/unit/bedrock/sps/SimpleRemoteCATest.cpp
index 649a004c1f330..8fd58190da0f1 100644
--- a/orc-rt/test/unit/bedrock/sps/SimpleRemoteCATest.cpp
+++ b/orc-rt/test/unit/bedrock/sps/SimpleRemoteCATest.cpp
@@ -48,6 +48,7 @@ class TestCA : public SimpleRemoteCA {
   using SimpleRemoteCA::takeAllCalls;
   using SimpleRemoteCA::takeCall;
 
+  using MsgHeader = SimpleRemoteCA::MsgHeader;
   using Opcode = SimpleRemoteCA::Opcode;
   using ResultKind = SimpleRemoteCA::ResultKind;
 
@@ -134,6 +135,37 @@ TEST(SimpleRemoteCATest, SetupMessageRoundTrips) {
   S.detach([] {});
 }
 
+TEST(SimpleRemoteCATest, MessageHeaderRoundTrips) {
+  // A distinct value in every field, so a swapped or truncated one shows up.
+  // The tag uses its top bits: it carries a handler address on a 64-bit peer.
+  char Buf[TestCA::MsgHeader::Size];
+  TestCA::MsgHeader::encode(Buf, TestCA::Opcode::Call, 0x0123456789abcdefULL,
+                            0xfedcba9876543210ULL, /*PayloadSize=*/7);
+
+  auto F = TestCA::MsgHeader::decode(Buf);
+  EXPECT_EQ(F.OpC, static_cast<uint64_t>(TestCA::Opcode::Call));
+  EXPECT_EQ(F.SeqNo, 0x0123456789abcdefULL);
+  EXPECT_EQ(F.Tag, 0xfedcba9876543210ULL);
+
+  // encode takes the payload size, decode reports the whole message: a reader
+  // holding the header needs to know how much is still to come.
+  EXPECT_EQ(F.MsgSize, TestCA::MsgHeader::Size + 7);
+}
+
+TEST(SimpleRemoteCATest, MessageHeaderRoundTripsWithNoPayload) {
+  // A message that is exactly a header. Nothing is left to read once it is
+  // decoded, which is the case a reader has to tell from a partial one.
+  char Buf[TestCA::MsgHeader::Size];
+  TestCA::MsgHeader::encode(Buf, TestCA::Opcode::Setup, /*SeqNo=*/0, /*Tag=*/0,
+                            /*PayloadSize=*/0);
+
+  auto F = TestCA::MsgHeader::decode(Buf);
+  EXPECT_EQ(F.OpC, static_cast<uint64_t>(TestCA::Opcode::Setup));
+  EXPECT_EQ(F.SeqNo, 0u);
+  EXPECT_EQ(F.Tag, 0u);
+  EXPECT_EQ(F.MsgSize, TestCA::MsgHeader::Size);
+}
+
 TEST(SimpleRemoteCATest, OrderlyHangupRoundTrips) {
   // Both ends encode and decode hang-ups through these, so a success value must
   // survive the trip as a success.



More information about the llvm-commits mailing list