[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