CIRCT 24.0.0git
Loading...
Searching...
No Matches
ESIRuntimeTypedPortsTest.cpp
Go to the documentation of this file.
1//===- ESIRuntimeTypedPortsTest.cpp - Typed ESI port tests ----------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "esi/TypedPorts.h"
10#include "gtest/gtest.h"
11
12#include <algorithm>
13#include <array>
14#include <chrono>
15#include <cstring>
16#include <deque>
17#include <vector>
18
19using namespace esi;
20
21namespace {
22
23TEST(TypedPortsTest, UnwrapTypeAlias) {
24 BitsType bits("i8", 8);
25 TypeAliasType alias("alias", "Alias", &bits);
26 TypeAliasType nested("nested", "Nested", &alias);
27 ChannelType channel("channel", &nested);
28 TypeAliasType channelAlias("channelAlias", "ChannelAlias", &channel);
29
30 EXPECT_EQ(unwrapTypeAlias(nullptr), nullptr);
31 EXPECT_EQ(unwrapTypeAlias(&bits), &bits);
32 EXPECT_EQ(unwrapTypeAlias(&nested), &bits);
33 EXPECT_EQ(unwrapTypeAlias(&channelAlias), &channel);
34}
35
36TEST(TypedPortsTest, GetUnwrappedAs) {
37 BitsType bits("i8", 8);
38 StructType payload("payload", {{"data", &bits}});
39 TypeAliasType alias("alias", "Alias", &payload);
40 TypeAliasType nestedAlias("nestedAlias", "NestedAlias", &alias);
41 ChannelType channel("channel", &nestedAlias);
42 TypeAliasType channelAlias("channelAlias", "ChannelAlias", &channel);
43 ChannelType outerChannel("outerChannel", &channelAlias);
44 ArrayType array("array", &payload, 2);
45
46 EXPECT_EQ(getUnwrappedAs<StructType>(nullptr), nullptr);
47 EXPECT_EQ(getUnwrappedAs<StructType>(&payload), &payload);
48 EXPECT_EQ(getUnwrappedAs<StructType>(&nestedAlias), &payload);
49 EXPECT_EQ(getUnwrappedAs<StructType>(&channel), &payload);
50 EXPECT_EQ(getUnwrappedAs<StructType>(&channelAlias), &payload);
51 EXPECT_EQ(getUnwrappedAs<StructType>(&outerChannel), &payload);
52 EXPECT_EQ(getUnwrappedAs<BitVectorType>(&outerChannel), nullptr);
53 EXPECT_EQ(getUnwrappedAs<StructType>(&array), nullptr);
54 EXPECT_EQ(getUnwrappedAs<ArrayType>(&array), &array);
55}
56
57//===----------------------------------------------------------------------===//
58// verifyTypeCompatibility tests
59//===----------------------------------------------------------------------===//
60
61TEST(TypedPortsTest, VoidTypeCompatibility) {
62 VoidType voidType("void");
63 EXPECT_NO_THROW(verifyTypeCompatibility<void>(&voidType));
64
65 // Non-void types should fail.
66 UIntType uint1("ui1", 1);
68
69 SIntType sint32("si32", 32);
70 EXPECT_THROW(verifyTypeCompatibility<void>(&sint32),
72}
73
74TEST(TypedPortsTest, BoolTypeCompatibility) {
75 BitsType bits1("i1", 1);
76 EXPECT_NO_THROW(verifyTypeCompatibility<bool>(&bits1));
77
78 // Width > 1 should fail.
79 BitsType bits8("i8", 8);
81
82 // Wrong type entirely should fail.
83 SIntType sint1("si1", 1);
85}
86
87TEST(TypedPortsTest, SignedIntTypeCompatibility) {
88 // int32_t can hold si17 (width 17, in range (16,32]).
89 SIntType sint17("si17", 17);
90 EXPECT_NO_THROW(verifyTypeCompatibility<int32_t>(&sint17));
91
92 // si32 has width 32, which fits exactly in int32_t. Should pass.
93 SIntType sint32("si32", 32);
94 EXPECT_NO_THROW(verifyTypeCompatibility<int32_t>(&sint32));
95
96 // si33 has width 33, which exceeds int32_t. Should fail.
97 SIntType sint33("si33", 33);
98 EXPECT_THROW(verifyTypeCompatibility<int32_t>(&sint33),
100
101 // si16 fits in int32_t but a smaller type (int16_t) would suffice. Reject.
102 SIntType sint16("si16", 16);
103 EXPECT_THROW(verifyTypeCompatibility<int32_t>(&sint16),
105
106 // si8 is even smaller — also reject for int32_t.
107 SIntType sint8("si8", 8);
108 EXPECT_THROW(verifyTypeCompatibility<int32_t>(&sint8),
110
111 // But si8 should be fine for int8_t (closest match).
112 EXPECT_NO_THROW(verifyTypeCompatibility<int8_t>(&sint8));
113
114 // UIntType should fail for signed C++ type.
115 UIntType uint31("ui31", 31);
116 EXPECT_THROW(verifyTypeCompatibility<int32_t>(&uint31),
118
119 // int64_t can hold si33 (width 33, in range (32,64]).
120 SIntType sint33b("si33", 33);
121 EXPECT_NO_THROW(verifyTypeCompatibility<int64_t>(&sint33b));
122
123 // si64 fits exactly in int64_t. Should pass.
124 SIntType sint64("si64", 64);
125 EXPECT_NO_THROW(verifyTypeCompatibility<int64_t>(&sint64));
126
127 // si65 exceeds int64_t. Should fail.
128 SIntType sint65("si65", 65);
129 EXPECT_THROW(verifyTypeCompatibility<int64_t>(&sint65),
131
132 // si32 fits in int64_t but int32_t would suffice. Reject.
133 EXPECT_THROW(verifyTypeCompatibility<int64_t>(&sint32),
135}
136
137TEST(TypedPortsTest, UnsignedIntTypeCompatibility) {
138 // uint32_t can hold ui17 (width 17, in range (16,32]).
139 UIntType uint17("ui17", 17);
140 EXPECT_NO_THROW(verifyTypeCompatibility<uint32_t>(&uint17));
141
142 // ui32 has width 32, which fits exactly in uint32_t. Should pass.
143 UIntType uint32_t_("ui32", 32);
144 EXPECT_NO_THROW(verifyTypeCompatibility<uint32_t>(&uint32_t_));
145
146 // ui33 exceeds uint32_t. Should fail.
147 UIntType uint33("ui33", 33);
148 EXPECT_THROW(verifyTypeCompatibility<uint32_t>(&uint33),
150
151 // ui16 fits but uint16_t would suffice. Reject for uint32_t.
152 UIntType uint16_("ui16", 16);
153 EXPECT_THROW(verifyTypeCompatibility<uint32_t>(&uint16_),
155
156 // But ui16 should be fine for uint16_t.
157 EXPECT_NO_THROW(verifyTypeCompatibility<uint16_t>(&uint16_));
158
159 // BitsType (signless iM) should also be accepted for unsigned.
160 BitsType bits17("i17", 17);
161 EXPECT_NO_THROW(verifyTypeCompatibility<uint32_t>(&bits17));
162
163 // BitsType with width 32 fits in uint32_t. Should pass.
164 BitsType bits32("i32", 32);
165 EXPECT_NO_THROW(verifyTypeCompatibility<uint32_t>(&bits32));
166
167 // BitsType with width 33 exceeds uint32_t. Should fail.
168 BitsType bits33("i33", 33);
169 EXPECT_THROW(verifyTypeCompatibility<uint32_t>(&bits33),
171
172 // BitsType width 8 should be rejected for uint32_t (uint8_t suffices).
173 BitsType bits8("i8", 8);
174 EXPECT_THROW(verifyTypeCompatibility<uint32_t>(&bits8),
176 EXPECT_NO_THROW(verifyTypeCompatibility<uint8_t>(&bits8));
177
178 // uint64_t with ui33 (in range (32,64]).
179 UIntType uint33b("ui33", 33);
180 EXPECT_NO_THROW(verifyTypeCompatibility<uint64_t>(&uint33b));
181
182 // uint64_t with ui64 fits exactly. Should pass.
183 UIntType uint64_t_("ui64", 64);
184 EXPECT_NO_THROW(verifyTypeCompatibility<uint64_t>(&uint64_t_));
185
186 // uint64_t with ui65 exceeds. Should fail.
187 UIntType uint65("ui65", 65);
188 EXPECT_THROW(verifyTypeCompatibility<uint64_t>(&uint65),
190
191 // uint64_t with ui32 — uint32_t would suffice. Reject.
192 EXPECT_THROW(verifyTypeCompatibility<uint64_t>(&uint32_t_),
194
195 // SIntType should fail for unsigned C++ type.
196 SIntType sint31("si31", 31);
197 EXPECT_THROW(verifyTypeCompatibility<uint32_t>(&sint31),
199}
200
201// Test struct with _ESI_ID.
202struct TestStruct {
203 static constexpr std::string_view _ESI_ID = "MyModule.TestStruct";
204 uint32_t field1;
205 uint16_t field2;
206};
207
208struct DeserializerWithESIID {
209 static constexpr std::string_view _ESI_ID = "MyModule.DeserializedStruct";
210
211 class TypeDeserializer
212 : public QueuedDecodeTypeDeserializer<DeserializerWithESIID> {
213 public:
214 using Base = QueuedDecodeTypeDeserializer<DeserializerWithESIID>;
215 using OutputCallback = Base::OutputCallback;
216 using DecodedOutputs = Base::DecodedOutputs;
217
218 explicit TypeDeserializer(OutputCallback output)
219 : Base(std::move(output)) {}
220
221 private:
222 DecodedOutputs decode(std::unique_ptr<SegmentedMessageData> &msg) override {
223 msg.reset();
224 return {};
225 }
226 };
227};
228
229TEST(TypedPortsTest, ESIIDTypeCompatibility) {
230 // Matching ID should pass.
231 StructType matchType("MyModule.TestStruct", {});
232 EXPECT_NO_THROW(verifyTypeCompatibility<TestStruct>(&matchType));
233
234 // Mismatched ID should fail.
235 StructType mismatchType("OtherModule.OtherStruct", {});
236 EXPECT_THROW(verifyTypeCompatibility<TestStruct>(&mismatchType),
238
239 // Even a non-struct type with matching ID should pass (ID comparison only).
240 UIntType uintWithMatchingID("MyModule.TestStruct", 32);
241 EXPECT_NO_THROW(verifyTypeCompatibility<TestStruct>(&uintWithMatchingID));
242}
243
244TEST(TypedPortsTest, NullPortTypeThrows) {
245 EXPECT_THROW(
246 verifyTypeCompatibility<int32_t>(static_cast<const Type *>(nullptr)),
248 EXPECT_THROW(
249 verifyTypeCompatibility<void>(static_cast<const Type *>(nullptr)),
251}
252
253// A type that is not integral and has no _ESI_ID — should hit fallback.
254struct UnknownCppType {
255 double x;
256};
257
258TEST(TypedPortsTest, FallbackThrows) {
259 UIntType uint32("ui32", 32);
260 EXPECT_THROW(verifyTypeCompatibility<UnknownCppType>(&uint32),
262}
263
264//===----------------------------------------------------------------------===//
265// TypedWritePort round-trip tests (verify MessageData encoding)
266//===----------------------------------------------------------------------===//
267
268// A minimal concrete WriteChannelPort for testing. Captures the last written
269// MessageData instead of sending it anywhere.
270class MockWritePort : public WriteChannelPort {
271public:
272 MockWritePort(const Type *type) : WriteChannelPort(type) {}
273
274 void connect(const ConnectOptions &opts = {}) override {
275 connectImpl(opts);
276 connected = true;
277 }
278 void disconnect() override { connected = false; }
279 bool isConnected() const override { return connected; }
280
281 MessageData lastWritten;
282 /// When false, tryWriteImpl() rejects writes (simulates a full buffer).
283 bool acceptTryWrites = true;
284
285protected:
286 void writeImpl(const MessageData &data) override { lastWritten = data; }
287 bool tryWriteImpl(const MessageData &data) override {
288 if (!acceptTryWrites)
289 return false;
290 lastWritten = data;
291 return true;
292 }
293
294private:
295 bool connected = false;
296};
297
298// A write port whose `tryWriteImpl()` accepts `credits` more frames. Unlike
299// `MockWritePort`, it uses the base `connect()`, so window translation works.
300class CreditedWritePort : public WriteChannelPort {
301public:
302 using WriteChannelPort::WriteChannelPort;
303
304 size_t credits = 0;
305 std::vector<MessageData> written;
306
307protected:
308 void writeImpl(const MessageData &data) override { written.push_back(data); }
309 bool tryWriteImpl(const MessageData &data) override {
310 if (credits == 0)
311 return false;
312 --credits;
313 written.push_back(data);
314 return true;
315 }
316};
317
318TEST(TypedPortsTest, TranslatedTryWriteKeepsNothingWhenRefused) {
319 UIntType u8("ui8", 8);
320 StructType into("into", {{"a", &u8}, {"b", &u8}});
321 // Two frames per message.
322 WindowType window("win", "win", &into, &into,
323 {{"f0", {{"a"}}}, {"f1", {{"b"}}}});
324 CreditedWritePort port(&window);
325 port.connect();
326 MessageData data(std::vector<uint8_t>{1, 2});
327
328 // The first frame is refused: nothing is sent or kept for a later flush.
329 EXPECT_FALSE(port.tryWrite(data));
330 EXPECT_TRUE(port.flush());
331 EXPECT_TRUE(port.written.empty());
332
333 // Only the first frame fits: the rest waits for `flush()`.
334 port.credits = 1;
335 EXPECT_TRUE(port.tryWrite(data));
336 EXPECT_EQ(port.written.size(), 1u);
337 EXPECT_FALSE(port.flush());
338 port.credits = 1;
339 EXPECT_TRUE(port.flush());
340 EXPECT_EQ(port.written.size(), 2u);
341}
342
343TEST(TypedPortsTest, TypedWritePortConnectThrowsOnMismatch) {
344 UIntType uint32("ui32", 32);
345 MockWritePort mock(&uint32);
346 TypedWritePort<int32_t> typed(mock); // int32_t expects SIntType
347 EXPECT_THROW(typed.connect(), AcceleratorMismatchError);
348}
349
350TEST(TypedPortsTest, TypedWritePortConnectSucceeds) {
351 SIntType sint31("si31", 31);
352 MockWritePort mock(&sint31);
353 TypedWritePort<int32_t> typed(mock);
354 EXPECT_NO_THROW(typed.connect());
355 EXPECT_TRUE(typed.isConnected());
356}
357
358TEST(TypedPortsTest, TypedWritePortRoundTrip) {
359 SIntType sint15("si15", 15);
360 MockWritePort mock(&sint15);
361 TypedWritePort<int16_t> typed(mock);
362 typed.connect();
363
364 int16_t val = 12345;
365 typed.write(val);
366
367 // Wire size for si15 is 2 bytes ((15+7)/8).
368 ASSERT_EQ(mock.lastWritten.getSize(), 2u);
369}
370
371TEST(TypedPortsTest, SignExtensionNonByteAligned) {
372 // Test fromMessageData sign extension for non-byte-aligned widths.
373 // si4 value -1 is wire 0x0F (4 bits: 1111). Sign bit is bit 3.
374 {
375 SIntType si4("si4", 4);
376 WireInfo wi = getWireInfo(&si4);
377 EXPECT_EQ(wi.bytes, 1u);
378 EXPECT_EQ(wi.bitWidth, 4u);
379 uint8_t wire = 0x0F; // -1 in si4
380 MessageData msg(&wire, 1);
381 int32_t val = fromMessageData<int32_t>(msg, wi);
382 EXPECT_EQ(val, -1);
383 }
384 // si4 value 7 is wire 0x07 (4 bits: 0111). Positive, no sign extension.
385 {
386 SIntType si4("si4", 4);
387 WireInfo wi = getWireInfo(&si4);
388 uint8_t wire = 0x07;
389 MessageData msg(&wire, 1);
390 int32_t val = fromMessageData<int32_t>(msg, wi);
391 EXPECT_EQ(val, 7);
392 }
393 // si22 value -1 is wire {0xFF, 0xFF, 0x3F} (22 bits all 1s).
394 // Sign bit is bit 21 = bit 5 of byte 2, mask 0x20.
395 {
396 SIntType si22("si22", 22);
397 WireInfo wi = getWireInfo(&si22);
398 EXPECT_EQ(wi.bytes, 3u);
399 uint8_t wire[3] = {0xFF, 0xFF, 0x3F};
400 MessageData msg(wire, 3);
401 int32_t val = fromMessageData<int32_t>(msg, wi);
402 EXPECT_EQ(val, -1);
403 }
404 // si22 positive value: 0x1FFFFF (all data bits 1, sign bit 0)
405 {
406 SIntType si22("si22", 22);
407 WireInfo wi = getWireInfo(&si22);
408 uint8_t wire[3] = {0xFF, 0xFF, 0x1F}; // bit 21 = 0
409 MessageData msg(wire, 3);
410 int32_t val = fromMessageData<int32_t>(msg, wi);
411 EXPECT_EQ(val, 0x1FFFFF); // 2097151
412 }
413}
414
415TEST(TypedPortsTest, TypedWritePortVoid) {
416 VoidType voidType("void");
417 MockWritePort mock(&voidType);
418 TypedWritePort<void> typed(mock);
419 EXPECT_NO_THROW(typed.connect());
420
421 typed.write();
422 ASSERT_EQ(mock.lastWritten.getSize(), 1u);
423 EXPECT_EQ(mock.lastWritten.getData()[0], 0);
424}
425
426//===----------------------------------------------------------------------===//
427// MockReadPort for TypedFunction testing
428//===----------------------------------------------------------------------===//
429
430// A minimal concrete ReadChannelPort that returns a preset response.
431class MockReadPort : public ReadChannelPort {
432public:
433 MockReadPort(const Type *type) : ReadChannelPort(type) {}
434
435 void connect(std::function<bool(MessageData)>,
436 const ConnectOptions & = {}) override {
437 mode = Mode::Callback;
438 }
439 void connect(const ConnectOptions & = {}) override { mode = Mode::Polling; }
440
441 void read(MessageData &outData) override { outData = nextResponse; }
442 std::future<MessageData> readAsync() override {
443 std::promise<MessageData> p;
444 p.set_value(nextResponse);
445 return p.get_future();
446 }
447
448 MessageData nextResponse;
449};
450
451TEST(TypedPortsTest, TypedReadPortCustomDeserializerVerifiesESIID) {
452 StructType matchType("MyModule.DeserializedStruct", {});
453 MockReadPort matching(&matchType);
455 EXPECT_NO_THROW(ok.connect());
456
457 StructType mismatchType("OtherModule.OtherStruct", {});
458 MockReadPort mismatch(&mismatchType);
460 EXPECT_THROW(bad.connect(), AcceleratorMismatchError);
461}
462
463class CallbackDrivenMockReadPort : public ReadChannelPort {
464public:
465 CallbackDrivenMockReadPort(const Type *type) : ReadChannelPort(type) {}
466
467 bool deliver(std::unique_ptr<SegmentedMessageData> msg) {
468 ++deliveryCount;
469 pending = std::move(msg);
470 return retryPending();
471 }
472
473 bool retryPending() {
474 if (!pending)
475 throw std::runtime_error(
476 "CallbackDrivenMockReadPort::retryPending with no message");
477 if (!invokeCallback(pending))
478 return false;
479 pending.reset();
480 return true;
481 }
482
483 bool hasPending() const { return static_cast<bool>(pending); }
484 size_t numActiveCallbacks() const { return activeCallbacks; }
485
486 size_t deliveryCount = 0;
487
488private:
489 std::unique_ptr<SegmentedMessageData> pending;
490};
491
492class ThrowOnCopyReadCallback {
493public:
494 explicit ThrowOnCopyReadCallback(std::shared_ptr<bool> shouldThrow)
495 : shouldThrow(std::move(shouldThrow)) {}
496
497 ThrowOnCopyReadCallback(const ThrowOnCopyReadCallback &other)
498 : shouldThrow(other.shouldThrow) {
499 if (*shouldThrow)
500 throw std::runtime_error("ThrowOnCopyReadCallback copy failure");
501 }
502
503 ThrowOnCopyReadCallback(ThrowOnCopyReadCallback &&) = default;
504 ThrowOnCopyReadCallback &operator=(const ThrowOnCopyReadCallback &) = default;
505 ThrowOnCopyReadCallback &operator=(ThrowOnCopyReadCallback &&) = default;
506
507 bool operator()(std::unique_ptr<SegmentedMessageData> &) const {
508 return true;
509 }
510
511private:
512 std::shared_ptr<bool> shouldThrow;
513};
514
515static MessageData packUint32Words(std::initializer_list<uint32_t> values) {
516 std::vector<uint8_t> bytes(values.size() * sizeof(uint32_t));
517 size_t offset = 0;
518 for (uint32_t value : values) {
519 std::memcpy(bytes.data() + offset, &value, sizeof(value));
520 offset += sizeof(value);
521 }
522 return MessageData(std::move(bytes));
523}
524
525struct BufferedSequence {
526 std::vector<uint32_t> values;
527
528 class TypeDeserializer
529 : public QueuedDecodeTypeDeserializer<BufferedSequence> {
530 public:
531 using Base = QueuedDecodeTypeDeserializer<BufferedSequence>;
532 using OutputCallback = Base::OutputCallback;
533 using DecodedOutputs = Base::DecodedOutputs;
534
535 explicit TypeDeserializer(OutputCallback output)
536 : Base(std::move(output)) {}
537
538 private:
539 DecodedOutputs decode(std::unique_ptr<SegmentedMessageData> &msg) override {
540 MessageData scratch;
541 const MessageData &flat =
542 detail::getMessageDataRef<BufferedSequence>(*msg, scratch);
543 if (flat.getSize() % sizeof(uint32_t) != 0)
544 throw std::runtime_error(
545 "BufferedSequence::TypeDeserializer: truncated word payload");
546
547 DecodedOutputs decoded;
548 for (size_t offset = 0; offset < flat.getSize();
549 offset += sizeof(uint32_t)) {
550 uint32_t value = 0;
551 std::memcpy(&value, flat.getBytes() + offset, sizeof(value));
552 auto sequence = std::make_unique<BufferedSequence>();
553 sequence->values.push_back(value);
554 decoded.push_back(std::move(sequence));
555 }
556 msg.reset();
557 return decoded;
558 }
559 };
560};
561
562TEST(TypedPortsTest, TypedReadPortPODBackpressuresAfterOneBufferedOutput) {
563 UIntType uint32("ui32", 32);
564 CallbackDrivenMockReadPort mock(&uint32);
565
566 TypedReadPort<uint32_t> typed(mock);
567 typed.connect();
568 typed.setMaxDataQueueMsgs(1);
569
570 EXPECT_TRUE(
571 mock.deliver(std::make_unique<MessageData>(packUint32Words({11}))));
572 // The second raw message is consumed into the POD deserializer's single-slot
573 // typed buffer even though the polling queue is full. Backpressure shows up
574 // on the next raw message boundary.
575 EXPECT_TRUE(
576 mock.deliver(std::make_unique<MessageData>(packUint32Words({22}))));
577 EXPECT_FALSE(mock.hasPending());
578 EXPECT_FALSE(
579 mock.deliver(std::make_unique<MessageData>(packUint32Words({33}))));
580 EXPECT_TRUE(mock.hasPending());
581
582 std::unique_ptr<uint32_t> first = typed.read();
583 ASSERT_TRUE(first);
584 EXPECT_EQ(*first, 11u);
585 EXPECT_TRUE(mock.retryPending());
586 EXPECT_FALSE(mock.hasPending());
587 std::unique_ptr<uint32_t> second = typed.read();
588 ASSERT_TRUE(second);
589 EXPECT_EQ(*second, 22u);
590 std::unique_ptr<uint32_t> third = typed.read();
591 ASSERT_TRUE(third);
592 EXPECT_EQ(*third, 33u);
593}
594
595TEST(TypedPortsTest, TypedReadPortPODRetriesSameOwnedObjectOnLaterPush) {
596 UIntType uint32("ui32", 32);
597 CallbackDrivenMockReadPort mock(&uint32);
598
599 TypedReadPort<uint32_t> typed(mock);
600 const uint32_t *firstObject = nullptr;
601 size_t callbackAttempts = 0;
602
603 typed.connect([&](std::unique_ptr<uint32_t> &value) {
604 ++callbackAttempts;
605 EXPECT_TRUE(value);
606 if (callbackAttempts == 1) {
607 EXPECT_EQ(*value, 11u);
608 firstObject = value.get();
609 return false;
610 }
611 if (callbackAttempts == 2) {
612 EXPECT_EQ(*value, 11u);
613 EXPECT_EQ(value.get(), firstObject);
614 return true;
615 }
616 EXPECT_EQ(*value, 22u);
617 return true;
618 });
619
620 EXPECT_TRUE(
621 mock.deliver(std::make_unique<MessageData>(packUint32Words({11}))));
622 EXPECT_FALSE(mock.hasPending());
623 // A later raw push first retries the buffered typed value, then handles the
624 // new message once that buffered value is accepted.
625 EXPECT_TRUE(
626 mock.deliver(std::make_unique<MessageData>(packUint32Words({22}))));
627 EXPECT_FALSE(mock.hasPending());
628 EXPECT_EQ(callbackAttempts, 3u);
629}
630
631TEST(TypedPortsTest, TypedReadPortCustomDeserializerPokesBlockedOutput) {
632 UIntType uint32("ui32", 32);
633 CallbackDrivenMockReadPort mock(&uint32);
634
636 typed.connect();
637 typed.setMaxDataQueueMsgs(1);
638
639 EXPECT_TRUE(
640 mock.deliver(std::make_unique<MessageData>(packUint32Words({10, 20}))));
641 EXPECT_EQ(mock.deliveryCount, 1u);
642
643 std::unique_ptr<BufferedSequence> first = typed.read();
644 ASSERT_TRUE(first);
645 ASSERT_EQ(first->values.size(), 1u);
646 EXPECT_EQ(first->values[0], 10u);
647
648 std::unique_ptr<BufferedSequence> second = typed.read();
649 ASSERT_TRUE(second);
650 ASSERT_EQ(second->values.size(), 1u);
651 EXPECT_EQ(second->values[0], 20u);
652 EXPECT_EQ(mock.deliveryCount, 1u);
653}
654
655TEST(TypedPortsTest,
656 TypedReadPortCustomDeserializerConsumesMultipleFramesPerRawMessage) {
657 UIntType uint32("ui32", 32);
658 CallbackDrivenMockReadPort mock(&uint32);
659
661 typed.connect();
662
663 std::future<std::unique_ptr<BufferedSequence>> first = typed.readAsync();
664 std::future<std::unique_ptr<BufferedSequence>> second = typed.readAsync();
665 std::future<std::unique_ptr<BufferedSequence>> third = typed.readAsync();
666
667 EXPECT_TRUE(
668 mock.deliver(std::make_unique<MessageData>(packUint32Words({1, 2, 3}))));
669
670 std::unique_ptr<BufferedSequence> firstValue = first.get();
671 ASSERT_TRUE(firstValue);
672 EXPECT_EQ(firstValue->values[0], 1u);
673
674 std::unique_ptr<BufferedSequence> secondValue = second.get();
675 ASSERT_TRUE(secondValue);
676 EXPECT_EQ(secondValue->values[0], 2u);
677
678 std::unique_ptr<BufferedSequence> thirdValue = third.get();
679 ASSERT_TRUE(thirdValue);
680 EXPECT_EQ(thirdValue->values[0], 3u);
681}
682
683TEST(TypedPortsTest,
684 TypedReadPortCustomDeserializerQueuesMultiplePendingOutputs) {
685 UIntType uint32("ui32", 32);
686 CallbackDrivenMockReadPort mock(&uint32);
687
689 typed.connect();
690 typed.setMaxDataQueueMsgs(1);
691
692 EXPECT_TRUE(
693 mock.deliver(std::make_unique<MessageData>(packUint32Words({7, 8, 9}))));
694 EXPECT_EQ(mock.deliveryCount, 1u);
695
696 std::unique_ptr<BufferedSequence> first = typed.read();
697 ASSERT_TRUE(first);
698 EXPECT_EQ(first->values[0], 7u);
699
700 std::unique_ptr<BufferedSequence> second = typed.read();
701 ASSERT_TRUE(second);
702 EXPECT_EQ(second->values[0], 8u);
703
704 std::unique_ptr<BufferedSequence> third = typed.read();
705 ASSERT_TRUE(third);
706 EXPECT_EQ(third->values[0], 9u);
707 EXPECT_EQ(mock.deliveryCount, 1u);
708}
709
710struct FragmentedCoord {
711 uint32_t y;
712 uint32_t x;
713};
714static_assert(sizeof(FragmentedCoord) == 8, "Size mismatch");
715
716static std::array<uint8_t, sizeof(FragmentedCoord)> packCoordBytes(uint32_t y,
717 uint32_t x) {
718 FragmentedCoord coord{y, x};
719 std::array<uint8_t, sizeof(FragmentedCoord)> bytes{};
720 std::memcpy(bytes.data(), &coord, sizeof(coord));
721 return bytes;
722}
723
724struct FragmentedCoordBatch {
725 std::vector<FragmentedCoord> coords;
726
727 class TypeDeserializer
728 : public QueuedDecodeTypeDeserializer<FragmentedCoordBatch> {
729 public:
730 using Base = QueuedDecodeTypeDeserializer<FragmentedCoordBatch>;
731 using OutputCallback = Base::OutputCallback;
732 using DecodedOutputs = Base::DecodedOutputs;
733
734 explicit TypeDeserializer(OutputCallback output)
735 : Base(std::move(output)) {}
736
737 private:
738 DecodedOutputs decode(std::unique_ptr<SegmentedMessageData> &msg) override {
739 MessageData scratch;
740 const MessageData &flat =
741 detail::getMessageDataRef<FragmentedCoordBatch>(*msg, scratch);
742
743 DecodedOutputs decoded;
744 const uint8_t *bytes = flat.getBytes();
745 size_t offset = 0;
746 while (offset < flat.getSize()) {
747 size_t needed = sizeof(FragmentedCoord) - partialFrameBytes.size();
748 size_t chunkSize = std::min(needed, flat.getSize() - offset);
749 partialFrameBytes.insert(partialFrameBytes.end(), bytes + offset,
750 bytes + offset + chunkSize);
751 offset += chunkSize;
752
753 if (partialFrameBytes.size() != sizeof(FragmentedCoord))
754 break;
755
756 FragmentedCoord coord;
757 std::memcpy(&coord, partialFrameBytes.data(), sizeof(coord));
758 partialFrameBytes.clear();
759
760 auto batch = std::make_unique<FragmentedCoordBatch>();
761 batch->coords.push_back(coord);
762 decoded.push_back(std::move(batch));
763 }
764
765 msg.reset();
766 return decoded;
767 }
768
769 std::vector<uint8_t> partialFrameBytes;
770 };
771};
772
773TEST(TypedPortsTest,
774 TypedReadPortCustomDeserializerConsumesSplitFramesAcrossRawMessages) {
775 UIntType uint32("ui32", 32);
776 CallbackDrivenMockReadPort mock(&uint32);
777
779 typed.connect();
780
781 std::future<std::unique_ptr<FragmentedCoordBatch>> first = typed.readAsync();
782 std::future<std::unique_ptr<FragmentedCoordBatch>> second = typed.readAsync();
783
784 std::array<uint8_t, sizeof(FragmentedCoord)> coordA = packCoordBytes(10, 20);
785 std::array<uint8_t, sizeof(FragmentedCoord)> coordB = packCoordBytes(30, 40);
786
787 std::vector<uint8_t> firstChunk(coordA.begin(), coordA.begin() + 6);
788 EXPECT_TRUE(mock.deliver(
789 std::make_unique<MessageData>(MessageData(std::move(firstChunk)))));
790
791 std::vector<uint8_t> secondChunk;
792 secondChunk.insert(secondChunk.end(), coordA.begin() + 6, coordA.end());
793 secondChunk.insert(secondChunk.end(), coordB.begin(), coordB.end());
794 EXPECT_TRUE(mock.deliver(
795 std::make_unique<MessageData>(MessageData(std::move(secondChunk)))));
796
797 std::unique_ptr<FragmentedCoordBatch> firstBatch = first.get();
798 ASSERT_TRUE(firstBatch);
799 ASSERT_EQ(firstBatch->coords.size(), 1u);
800 EXPECT_EQ(firstBatch->coords[0].y, 10u);
801 EXPECT_EQ(firstBatch->coords[0].x, 20u);
802
803 std::unique_ptr<FragmentedCoordBatch> secondBatch = second.get();
804 ASSERT_TRUE(secondBatch);
805 ASSERT_EQ(secondBatch->coords.size(), 1u);
806 EXPECT_EQ(secondBatch->coords[0].y, 30u);
807 EXPECT_EQ(secondBatch->coords[0].x, 40u);
808}
809//===----------------------------------------------------------------------===//
810// TypedFunction tests
811//===----------------------------------------------------------------------===//
812
813TEST(TypedPortsTest, TypedFunctionNullThrowsAtConnect) {
814 // Null is accepted at construction but throws at connect().
816 EXPECT_THROW(typed.connect(), AcceleratorMismatchError);
817}
818
819TEST(TypedPortsTest, TypedFunctionCallBeforeConnectThrows) {
820 // call() before connect() must throw, not dereference a null optional.
822 EXPECT_THROW(typed.call(0u).get(), std::runtime_error);
823}
824
825TEST(TypedPortsTest, TypedFunctionDoubleConnectThrows) {
826 // A second connect() on an already-connected wrapper must throw rather
827 // than silently rebuild internal state.
828 SIntType argInner("si24", 24);
829 ChannelType argChanType("channel<si24>", &argInner);
830 UIntType resultInner("ui15", 15);
831 ChannelType resultChanType("channel<ui15>", &resultInner);
832
833 BundleType::ChannelVector channels = {
834 {"arg", BundleType::Direction::To, &argChanType},
835 {"result", BundleType::Direction::From, &resultChanType},
836 };
837 BundleType bundleType("func_bundle", channels);
838
839 MockWritePort mockWrite(&argInner);
840 CallbackDrivenMockReadPort mockRead(&resultInner);
841
842 auto *func = services::FuncService::Function::get(AppID("test"), &bundleType,
843 mockWrite, mockRead);
844
846 typed.connect();
847 EXPECT_THROW(typed.connect(), std::runtime_error);
848 delete func;
849}
850
851TEST(TypedPortsTest, TypedFunctionConnectVerifiesTypes) {
852 // Create channel types matching si24 arg and ui16 result.
853 SIntType argInner("si24", 24);
854 ChannelType argChanType("channel<si24>", &argInner);
855 UIntType resultInner("ui15", 15);
856 ChannelType resultChanType("channel<ui15>", &resultInner);
857
858 BundleType::ChannelVector channels = {
859 {"arg", BundleType::Direction::To, &argChanType},
860 {"result", BundleType::Direction::From, &resultChanType},
861 };
862 BundleType bundleType("func_bundle", channels);
863
864 MockWritePort mockWrite(&argInner);
865 MockReadPort mockRead(&resultInner);
866
867 auto *func = services::FuncService::Function::get(AppID("test"), &bundleType,
868 mockWrite, mockRead);
869
870 // int32_t arg (signed) against si24 — should pass.
871 // uint16_t result (unsigned) against ui15 — should pass.
873 EXPECT_NO_THROW(typed.connect());
874 delete func;
875}
876
877TEST(TypedPortsTest, TypedFunctionConnectRejectsArgMismatch) {
878 UIntType argInner("ui24", 24);
879 ChannelType argChanType("channel<ui24>", &argInner);
880 UIntType resultInner("ui15", 15);
881 ChannelType resultChanType("channel<ui15>", &resultInner);
882
883 BundleType::ChannelVector channels = {
884 {"arg", BundleType::Direction::To, &argChanType},
885 {"result", BundleType::Direction::From, &resultChanType},
886 };
887 BundleType bundleType("func_bundle", channels);
888
889 MockWritePort mockWrite(&argInner);
890 MockReadPort mockRead(&resultInner);
891
892 auto *func = services::FuncService::Function::get(AppID("test"), &bundleType,
893 mockWrite, mockRead);
894
895 // int32_t (signed) against UIntType — should fail at connect.
897 EXPECT_THROW(typed.connect(), AcceleratorMismatchError);
898 delete func;
899}
900
901TEST(TypedPortsTest, TypedFunctionCallRoundTrip) {
902 SIntType argInner("si24", 24);
903 ChannelType argChanType("channel<si24>", &argInner);
904 UIntType resultInner("ui15", 15);
905 ChannelType resultChanType("channel<ui15>", &resultInner);
906
907 BundleType::ChannelVector channels = {
908 {"arg", BundleType::Direction::To, &argChanType},
909 {"result", BundleType::Direction::From, &resultChanType},
910 };
911 BundleType bundleType("func_bundle", channels);
912
913 MockWritePort mockWrite(&argInner);
914 CallbackDrivenMockReadPort mockRead(&resultInner);
915
916 auto *func = services::FuncService::Function::get(AppID("test"), &bundleType,
917 mockWrite, mockRead);
918
920 typed.connect();
921
922 int32_t arg = 100;
923 std::future<uint16_t> f = typed.call(arg);
924 // Deliver the response frame to satisfy the future. ui15 wire width is 2
925 // bytes so a 2-byte payload is required.
926 uint16_t expected = 42;
927 EXPECT_TRUE(mockRead.deliver(std::make_unique<MessageData>(MessageData(
928 reinterpret_cast<const uint8_t *>(&expected), sizeof(expected)))));
929 uint16_t result = f.get();
930 EXPECT_EQ(result, 42);
931
932 // Verify the written arg matches — si24 wire size is 3 bytes.
933 ASSERT_EQ(mockWrite.lastWritten.getSize(), 3u);
934 delete func;
935}
936
937//===----------------------------------------------------------------------===//
938// SegmentedMessageData tests via TypedWritePort
939//===----------------------------------------------------------------------===//
940
941/// A minimal SegmentedMessageData for testing.
942struct TestSegmented : public SegmentedMessageData {
943#pragma pack(push, 1)
944 struct Header {
945 uint32_t a;
946 uint16_t b;
947 };
948#pragma pack(pop)
949
950 Header header;
951 std::vector<uint32_t> items;
952
953 size_t numSegments() const override { return 2; }
954 Segment segment(size_t idx) const override {
955 if (idx == 0)
956 return {reinterpret_cast<const uint8_t *>(&header), sizeof(Header)};
957 return {reinterpret_cast<const uint8_t *>(items.data()),
958 items.size() * sizeof(uint32_t)};
959 }
960};
961
962TEST(TypedPortsTest, ReadChannelPortSegmentedCallbackRetriesSameMessageObject) {
963 UIntType uint32("ui32", 32);
964 CallbackDrivenMockReadPort mock(&uint32);
965
966 uint32_t expected = 0x12345678;
967 const uint8_t *firstBytes = nullptr;
968 size_t callbackCalls = 0;
969
970 mock.connect([&](std::unique_ptr<SegmentedMessageData> &msg) -> bool {
971 ++callbackCalls;
972 EXPECT_EQ(msg->numSegments(), 1u);
973
974 auto *flat = dynamic_cast<MessageData *>(msg.get());
975 EXPECT_NE(flat, nullptr);
976 if (!flat)
977 return false;
978 EXPECT_EQ(*flat->as<uint32_t>(), expected);
979
980 if (callbackCalls == 1)
981 firstBytes = flat->getBytes();
982 else
983 EXPECT_EQ(flat->getBytes(), firstBytes);
984
985 return callbackCalls == 2;
986 });
987
988 EXPECT_FALSE(
989 mock.deliver(std::make_unique<MessageData>(MessageData::from(expected))));
990 EXPECT_TRUE(mock.hasPending());
991 EXPECT_TRUE(mock.retryPending());
992 EXPECT_FALSE(mock.hasPending());
993 EXPECT_EQ(callbackCalls, 2u);
994}
995
996TEST(TypedPortsTest, ReadChannelPortFlatCallbackFlattensSegmentedMessageRetry) {
997 UIntType uint32("ui32", 32);
998 CallbackDrivenMockReadPort mock(&uint32);
999
1000 TestSegmented input;
1001 input.header.a = 0x12345678;
1002 input.header.b = 0xABCD;
1003 input.items = {1, 2, 3};
1004 MessageData expected = input.toMessageData();
1005
1006 size_t callbackCalls = 0;
1007 mock.connect([&](MessageData data) {
1008 ++callbackCalls;
1009 EXPECT_EQ(data.getData(), expected.getData());
1010 return callbackCalls == 2;
1011 });
1012
1013 EXPECT_FALSE(mock.deliver(std::make_unique<TestSegmented>(input)));
1014 EXPECT_TRUE(mock.hasPending());
1015 EXPECT_TRUE(mock.retryPending());
1016 EXPECT_FALSE(mock.hasPending());
1017 EXPECT_EQ(callbackCalls, 2u);
1018}
1019
1020TEST(TypedPortsTest, ReadChannelPortPollingRetriesFlattenedSegmentedMessage) {
1021 UIntType uint32("ui32", 32);
1022 CallbackDrivenMockReadPort mock(&uint32);
1023 mock.connect();
1024 mock.setMaxDataQueueMsgs(1);
1025
1026 TestSegmented firstInput;
1027 firstInput.header.a = 0xAAAA5555;
1028 firstInput.header.b = 0x1357;
1029 firstInput.items = {10};
1030 MessageData firstExpected = firstInput.toMessageData();
1031
1032 TestSegmented secondInput;
1033 secondInput.header.a = 0xDEADBEEF;
1034 secondInput.header.b = 0x2468;
1035 secondInput.items = {20, 30};
1036 MessageData secondExpected = secondInput.toMessageData();
1037
1038 EXPECT_TRUE(mock.deliver(std::make_unique<TestSegmented>(firstInput)));
1039 EXPECT_FALSE(mock.deliver(std::make_unique<TestSegmented>(secondInput)));
1040 EXPECT_TRUE(mock.hasPending());
1041
1042 MessageData firstOut;
1043 mock.read(firstOut);
1044 EXPECT_EQ(firstOut.getData(), firstExpected.getData());
1045
1046 EXPECT_TRUE(mock.retryPending());
1047 EXPECT_FALSE(mock.hasPending());
1048
1049 MessageData secondOut;
1050 mock.read(secondOut);
1051 EXPECT_EQ(secondOut.getData(), secondExpected.getData());
1052}
1053
1054TEST(TypedPortsTest, ReadChannelPortPollingReadAsyncThrowsWhenDisconnected) {
1055 UIntType uint32("ui32", 32);
1056 CallbackDrivenMockReadPort mock(&uint32);
1057
1058 EXPECT_THROW(mock.readAsync(), std::runtime_error);
1059
1060 mock.connect();
1061 std::future<MessageData> pending = mock.readAsync();
1062 mock.disconnect();
1063
1064 EXPECT_EQ(pending.wait_for(std::chrono::milliseconds(0)),
1065 std::future_status::ready);
1066 EXPECT_THROW(pending.get(), std::future_error);
1067 EXPECT_THROW(mock.readAsync(), std::runtime_error);
1068
1069 mock.connect();
1070 EXPECT_TRUE(
1071 mock.deliver(std::make_unique<MessageData>(packUint32Words({11}))));
1072 MessageData out;
1073 mock.read(out);
1074 EXPECT_EQ(*out.as<uint32_t>(), 11u);
1075}
1076
1077TEST(TypedPortsTest, ReadChannelPortPollingConnectRejectsReconnect) {
1078 UIntType uint32("ui32", 32);
1079 CallbackDrivenMockReadPort mock(&uint32);
1080
1081 mock.connect();
1082 EXPECT_THROW(mock.connect(), std::runtime_error);
1083}
1084
1085TEST(TypedPortsTest, ReadChannelPortDisconnectRevokesCallback) {
1086 UIntType uint32("ui32", 32);
1087 CallbackDrivenMockReadPort mock(&uint32);
1088
1089 mock.connect();
1090 mock.disconnect();
1091
1092 EXPECT_FALSE(
1093 mock.deliver(std::make_unique<MessageData>(packUint32Words({11}))));
1094 EXPECT_TRUE(mock.hasPending());
1095
1096 mock.connect();
1097 EXPECT_TRUE(mock.retryPending());
1098 EXPECT_FALSE(mock.hasPending());
1099
1100 MessageData out;
1101 mock.read(out);
1102 EXPECT_EQ(*out.as<uint32_t>(), 11u);
1103}
1104
1105TEST(TypedPortsTest, TypedReadPortDestructorDisconnectsRawPort) {
1106 UIntType uint32("ui32", 32);
1107 CallbackDrivenMockReadPort mock(&uint32);
1108
1109 {
1110 TypedReadPort<uint32_t> typed(mock);
1111 typed.connect();
1112 EXPECT_TRUE(mock.isConnected());
1113 }
1114
1115 EXPECT_FALSE(mock.isConnected());
1116 EXPECT_FALSE(
1117 mock.deliver(std::make_unique<MessageData>(packUint32Words({11}))));
1118 EXPECT_TRUE(mock.hasPending());
1119
1120 TypedReadPort<uint32_t> reconnected(mock);
1121 reconnected.connect();
1122 EXPECT_TRUE(mock.retryPending());
1123 EXPECT_FALSE(mock.hasPending());
1124
1125 std::unique_ptr<uint32_t> out = reconnected.read();
1126 ASSERT_TRUE(out);
1127 EXPECT_EQ(*out, 11u);
1128}
1129
1130TEST(TypedPortsTest,
1131 ReadChannelPortInvokeCallbackMaintainsCountOnCallbackCopyFailure) {
1132 UIntType uint32("ui32", 32);
1133 CallbackDrivenMockReadPort mock(&uint32);
1134 auto shouldThrow = std::make_shared<bool>(false);
1135
1136 mock.connect(ThrowOnCopyReadCallback(shouldThrow));
1137 *shouldThrow = true;
1138
1139 EXPECT_THROW(
1140 mock.deliver(std::make_unique<MessageData>(packUint32Words({11}))),
1141 std::runtime_error);
1142 EXPECT_EQ(mock.numActiveCallbacks(), 0u);
1143}
1144
1145TEST(TypedPortsTest, TypedWritePortSegmentedMessageData) {
1146 // Use any type — SegmentedMessageData skips type checks.
1147 UIntType uint32("ui32", 32);
1148 MockWritePort mock(&uint32);
1149
1151 typed.connect();
1152
1153 TestSegmented msg;
1154 msg.header.a = 0x12345678;
1155 msg.header.b = 0xABCD;
1156 msg.items = {1, 2, 3};
1157
1158 // write(const T&) should flatten via toMessageData().
1159 typed.write(msg);
1160
1161 // Expected: 6 bytes header + 12 bytes data = 18 bytes.
1162 EXPECT_EQ(mock.lastWritten.getSize(), 18u);
1163
1164 // Verify header bytes.
1165 const uint8_t *bytes = mock.lastWritten.getBytes();
1166 uint32_t gotA;
1167 uint16_t gotB;
1168 std::memcpy(&gotA, bytes, 4);
1169 std::memcpy(&gotB, bytes + 4, 2);
1170 EXPECT_EQ(gotA, 0x12345678u);
1171 EXPECT_EQ(gotB, 0xABCDu);
1172
1173 // Verify item bytes.
1174 uint32_t gotItems[3];
1175 std::memcpy(gotItems, bytes + 6, 12);
1176 EXPECT_EQ(gotItems[0], 1u);
1177 EXPECT_EQ(gotItems[1], 2u);
1178 EXPECT_EQ(gotItems[2], 3u);
1179}
1180
1181TEST(TypedPortsTest, WriteChannelPortTryWriteSegmented) {
1182 UIntType uint32("ui32", 32);
1183 MockWritePort mock(&uint32);
1184 mock.connect();
1185
1186 auto input = std::make_unique<TestSegmented>();
1187 input->header.a = 0x12345678;
1188 input->header.b = 0xABCD;
1189 input->items = {1, 2, 3};
1190 MessageData expected = input->toMessageData();
1191 SegmentedMessageData *raw = input.get();
1192 std::unique_ptr<SegmentedMessageData> msg = std::move(input);
1193
1194 // Rejected: the message must be left with the caller for a retry.
1195 mock.acceptTryWrites = false;
1196 EXPECT_FALSE(mock.tryWrite(msg));
1197 ASSERT_NE(msg, nullptr);
1198 EXPECT_EQ(msg.get(), raw);
1199 EXPECT_EQ(mock.lastWritten.getSize(), 0u);
1200
1201 // Accepted: ownership is taken and the flattened bytes are written.
1202 mock.acceptTryWrites = true;
1203 EXPECT_TRUE(mock.tryWrite(msg));
1204 EXPECT_EQ(msg, nullptr);
1205 EXPECT_EQ(mock.lastWritten.getData(), expected.getData());
1206
1207 std::unique_ptr<SegmentedMessageData> null;
1208 EXPECT_THROW(mock.tryWrite(null), std::runtime_error);
1209}
1210
1211TEST(TypedPortsTest, TypedWritePortTryWriteSegmentedUniquePtr) {
1212 UIntType uint32("ui32", 32);
1213 MockWritePort mock(&uint32);
1214
1216 typed.connect();
1217
1218 auto msg = std::make_unique<TestSegmented>();
1219 msg->header.a = 42;
1220 msg->header.b = 7;
1221 msg->items = {100, 200};
1222 MessageData expected = msg->toMessageData();
1223 TestSegmented *raw = msg.get();
1224
1225 mock.acceptTryWrites = false;
1226 EXPECT_FALSE(typed.tryWrite(msg));
1227 ASSERT_NE(msg, nullptr);
1228 EXPECT_EQ(msg.get(), raw);
1229 EXPECT_EQ(msg->items.size(), 2u);
1230
1231 mock.acceptTryWrites = true;
1232 EXPECT_TRUE(typed.tryWrite(msg));
1233 EXPECT_EQ(msg, nullptr);
1234 EXPECT_EQ(mock.lastWritten.getData(), expected.getData());
1235
1236 std::unique_ptr<TestSegmented> null;
1237 EXPECT_THROW(typed.tryWrite(null), std::runtime_error);
1238}
1239
1240TEST(TypedPortsTest, TypedWritePortTryWriteNonSegmentedUniquePtr) {
1241 UIntType uint32("ui32", 32);
1242 MockWritePort mock(&uint32);
1243
1244 TypedWritePort<uint32_t> typed(mock);
1245 typed.connect();
1246
1247 auto value = std::make_unique<uint32_t>(0xDEADBEEF);
1248
1249 mock.acceptTryWrites = false;
1250 EXPECT_FALSE(typed.tryWrite(value));
1251 ASSERT_NE(value, nullptr);
1252 EXPECT_EQ(*value, 0xDEADBEEFu);
1253
1254 mock.acceptTryWrites = true;
1255 EXPECT_TRUE(typed.tryWrite(value));
1256 EXPECT_EQ(value, nullptr);
1257 ASSERT_EQ(mock.lastWritten.getSize(), 4u);
1258 EXPECT_EQ(*mock.lastWritten.as<uint32_t>(), 0xDEADBEEFu);
1259}
1260
1261TEST(TypedPortsTest, TypedWritePortSegmentedNoTypeCheck) {
1262 // SegmentedMessageData type against a mismatched port type — works because
1263 // SkipTypeCheck=true bypasses verifyTypeCompatibility entirely.
1264 SIntType sint8("si8", 8);
1265 MockWritePort mock(&sint8);
1266
1268 EXPECT_NO_THROW(typed.connect());
1269
1270 TestSegmented msg;
1271 msg.header.a = 42;
1272 msg.header.b = 7;
1273 msg.items = {100};
1274
1275 typed.write(msg);
1276 // 6 bytes header + 4 bytes data = 10 bytes.
1277 EXPECT_EQ(mock.lastWritten.getSize(), 10u);
1278}
1279
1280TEST(TypedPortsTest, TypedFunctionSegmentedArg) {
1281 // Arg type is SegmentedMessageData — type check is skipped for it.
1282 // Use an arbitrary inner type for the arg channel since it won't be checked.
1283 UIntType argInner("ui32", 32);
1284 ChannelType argChanType("channel<ui32>", &argInner);
1285 UIntType resultInner("ui16", 16);
1286 ChannelType resultChanType("channel<ui16>", &resultInner);
1287
1288 BundleType::ChannelVector channels = {
1289 {"arg", BundleType::Direction::To, &argChanType},
1290 {"result", BundleType::Direction::From, &resultChanType},
1291 };
1292 BundleType bundleType("func_bundle", channels);
1293
1294 MockWritePort mockWrite(&argInner);
1295 CallbackDrivenMockReadPort mockRead(&resultInner);
1296
1297 auto *func = services::FuncService::Function::get(AppID("test"), &bundleType,
1298 mockWrite, mockRead);
1299
1301 typed.connect();
1302
1303 TestSegmented arg;
1304 arg.header.a = 0xDEAD;
1305 arg.header.b = 0xBE;
1306 arg.items = {10, 20};
1307
1308 std::future<uint16_t> f = typed.call(arg);
1309 // Deliver the response frame to satisfy the call's future.
1310 uint16_t expected = 99;
1311 EXPECT_TRUE(mockRead.deliver(std::make_unique<MessageData>(MessageData(
1312 reinterpret_cast<const uint8_t *>(&expected), sizeof(expected)))));
1313 uint16_t result = f.get();
1314 EXPECT_EQ(result, 99u);
1315
1316 // Verify the flattened arg: 6 bytes header + 8 bytes items = 14 bytes.
1317 EXPECT_EQ(mockWrite.lastWritten.getSize(), 14u);
1318
1319 const uint8_t *bytes = mockWrite.lastWritten.getBytes();
1320 uint32_t gotA;
1321 std::memcpy(&gotA, bytes, 4);
1322 EXPECT_EQ(gotA, 0xDEADu);
1323
1324 uint32_t gotItem0, gotItem1;
1325 std::memcpy(&gotItem0, bytes + 6, 4);
1326 std::memcpy(&gotItem1, bytes + 10, 4);
1327 EXPECT_EQ(gotItem0, 10u);
1328 EXPECT_EQ(gotItem1, 20u);
1329
1330 delete func;
1331}
1332
1333// A plain struct without _ESI_ID — not integral, not void, not bool.
1334struct UnrecognizedCppType {
1335 double x;
1336 double y;
1337};
1338
1339TEST(TypedPortsTest, VerifyTypeCompatibilityThrowsForUnsupportedType) {
1340 UIntType uint32("ui32", 32);
1343
1344 SIntType sint16("si16", 16);
1347}
1348
1349//===----------------------------------------------------------------------===//
1350// TypedFunction custom-deserializer tests
1351//===----------------------------------------------------------------------===//
1352
1353// Simple TypeDeserializer that decodes a single uint32 from one frame and
1354// emits exactly one OneWord value per push. Used to exercise the typical
1355// 1-frame-in / 1-typed-value-out custom path.
1356//
1357// Implements the standard backpressure/retry contract: a decoded `OneWord`
1358// is buffered until the output callback accepts it; only then is the raw
1359// message consumed. If `output()` returns false, the same raw message will
1360// be retried later, and a follow-up `poke()` retries delivery of the
1361// already-decoded value without re-decoding.
1362struct OneWord {
1363 uint32_t value;
1364
1365 class TypeDeserializer {
1366 public:
1367 using OutputCallback = detail::TypedReadOwnedCallback<OneWord>;
1368
1369 explicit TypeDeserializer(OutputCallback output)
1370 : output(std::move(output)) {}
1371
1372 bool push(std::unique_ptr<SegmentedMessageData> &msg) {
1373 // First, try to flush any previously-decoded value blocked on output.
1374 if (pending) {
1375 if (!output(pending))
1376 return false;
1377 pending.reset();
1378 }
1379
1380 MessageData scratch;
1381 const MessageData &flat =
1382 detail::getMessageDataRef<OneWord>(*msg, scratch);
1383 if (flat.getSize() != sizeof(uint32_t))
1384 throw std::runtime_error("OneWord: bad size");
1385 pending = std::make_unique<OneWord>();
1386 std::memcpy(&pending->value, flat.getBytes(), sizeof(uint32_t));
1387 // Raw message is consumed regardless of whether the typed output is
1388 // immediately accepted.
1389 msg.reset();
1390 if (output(pending))
1391 pending.reset();
1392 return true;
1393 }
1394
1395 bool poke() {
1396 if (pending && output(pending)) {
1397 pending.reset();
1398 return true;
1399 }
1400 return false;
1401 }
1402
1403 private:
1404 OutputCallback output;
1405 std::unique_ptr<OneWord> pending;
1406 };
1407};
1408
1409TEST(TypedPortsTest, TypedFunctionCustomDeserializerSingleFrame) {
1410 UIntType argInner("ui32", 32);
1411 ChannelType argChanType("channel<ui32>", &argInner);
1412 UIntType resultInner("ui32", 32);
1413 ChannelType resultChanType("channel<ui32>", &resultInner);
1414
1415 BundleType::ChannelVector channels = {
1416 {"arg", BundleType::Direction::To, &argChanType},
1417 {"result", BundleType::Direction::From, &resultChanType},
1418 };
1419 BundleType bundleType("func_bundle", channels);
1420
1421 MockWritePort mockWrite(&argInner);
1422 CallbackDrivenMockReadPort mockRead(&resultInner);
1423
1424 auto *func = services::FuncService::Function::get(AppID("test"), &bundleType,
1425 mockWrite, mockRead);
1426
1428 typed.connect();
1429
1430 std::future<OneWord> f = typed.call(0u);
1431 // Deliver one frame containing a single uint32; the deserializer produces
1432 // one OneWord which fulfills the call's pending future.
1433 uint32_t expected = 0xCAFE;
1434 EXPECT_TRUE(mockRead.deliver(std::make_unique<MessageData>(MessageData(
1435 reinterpret_cast<const uint8_t *>(&expected), sizeof(expected)))));
1436
1437 OneWord result = f.get();
1438 EXPECT_EQ(result.value, expected);
1439
1440 delete func;
1441}
1442
1443TEST(TypedPortsTest, TypedFunctionCustomDeserializerReadsMultipleFrames) {
1444 // FragmentedCoordBatch's TypeDeserializer emits one batch per 8-byte
1445 // FragmentedCoord. By feeding partial frames across multiple raw messages,
1446 // we exercise the deserializer's persistent partial-buffer state across
1447 // raw callback invocations.
1448 UIntType argInner("ui32", 32);
1449 ChannelType argChanType("channel<ui32>", &argInner);
1450 UIntType resultInner("ui32", 32);
1451 ChannelType resultChanType("channel<ui32>", &resultInner);
1452
1453 BundleType::ChannelVector channels = {
1454 {"arg", BundleType::Direction::To, &argChanType},
1455 {"result", BundleType::Direction::From, &resultChanType},
1456 };
1457 BundleType bundleType("func_bundle", channels);
1458
1459 MockWritePort mockWrite(&argInner);
1460 CallbackDrivenMockReadPort mockRead(&resultInner);
1461
1462 auto *func = services::FuncService::Function::get(AppID("test"), &bundleType,
1463 mockWrite, mockRead);
1464
1466 typed.connect();
1467
1468 std::future<FragmentedCoordBatch> f = typed.call(0u);
1469
1470 // Split a single 8-byte FragmentedCoord across two raw frames.
1471 std::array<uint8_t, sizeof(FragmentedCoord)> coordBytes =
1472 packCoordBytes(/*y=*/55, /*x=*/77);
1473 std::vector<uint8_t> firstChunk(coordBytes.begin(), coordBytes.begin() + 6);
1474 std::vector<uint8_t> secondChunk(coordBytes.begin() + 6, coordBytes.end());
1475 EXPECT_TRUE(
1476 mockRead.deliver(std::make_unique<MessageData>(std::move(firstChunk))));
1477 EXPECT_TRUE(
1478 mockRead.deliver(std::make_unique<MessageData>(std::move(secondChunk))));
1479
1480 FragmentedCoordBatch batch = f.get();
1481 ASSERT_EQ(batch.coords.size(), 1u);
1482 EXPECT_EQ(batch.coords[0].y, 55u);
1483 EXPECT_EQ(batch.coords[0].x, 77u);
1484
1485 delete func;
1486}
1487
1488TEST(TypedPortsTest, TypedFunctionCustomDeserializerSkipsResultTypeCheck) {
1489 // OneWord has no _ESI_ID, so the result-side type check should be skipped
1490 // and we should be able to connect against an arbitrarily-typed result
1491 // channel without a thrown AcceleratorMismatchError.
1492 UIntType argInner("ui32", 32);
1493 ChannelType argChanType("channel<ui32>", &argInner);
1494 // Use a wildly mismatched result type to prove the check is skipped.
1495 StructType resultInner("Anything", {});
1496 ChannelType resultChanType("channel<Anything>", &resultInner);
1497
1498 BundleType::ChannelVector channels = {
1499 {"arg", BundleType::Direction::To, &argChanType},
1500 {"result", BundleType::Direction::From, &resultChanType},
1501 };
1502 BundleType bundleType("func_bundle", channels);
1503
1504 MockWritePort mockWrite(&argInner);
1505 CallbackDrivenMockReadPort mockRead(&resultInner);
1506
1507 auto *func = services::FuncService::Function::get(AppID("test"), &bundleType,
1508 mockWrite, mockRead);
1509
1511 EXPECT_NO_THROW(typed.connect());
1512
1513 delete func;
1514}
1515
1516TEST(TypedPortsTest, TypedFunctionPipelinedCallsGetOutOfOrder) {
1517 // Two pipelined calls, each with a multi-frame response decoded by the
1518 // shared deserializer. Even when the second future's get() is called
1519 // before the first, each future must yield its own call's result -- not
1520 // the other call's frames. This exercises the FIFO contract of the
1521 // TypedReadPort polling buffer that backs `call()`.
1522 UIntType argInner("ui32", 32);
1523 ChannelType argChanType("channel<ui32>", &argInner);
1524 UIntType resultInner("ui32", 32);
1525 ChannelType resultChanType("channel<ui32>", &resultInner);
1526
1527 BundleType::ChannelVector channels = {
1528 {"arg", BundleType::Direction::To, &argChanType},
1529 {"result", BundleType::Direction::From, &resultChanType},
1530 };
1531 BundleType bundleType("func_bundle", channels);
1532
1533 MockWritePort mockWrite(&argInner);
1534 CallbackDrivenMockReadPort mockRead(&resultInner);
1535
1536 auto *func = services::FuncService::Function::get(AppID("test"), &bundleType,
1537 mockWrite, mockRead);
1538
1540 typed.connect();
1541
1542 // Issue two calls before any frames arrive; futures reserve slots in the
1543 // polling buffer in call FIFO order.
1544 std::future<FragmentedCoordBatch> f1 = typed.call(0u);
1545 std::future<FragmentedCoordBatch> f2 = typed.call(0u);
1546
1547 auto deliverCoord = [&](uint32_t y, uint32_t x) {
1548 auto bytes = packCoordBytes(y, x);
1549 std::vector<uint8_t> first(bytes.begin(), bytes.begin() + 6);
1550 std::vector<uint8_t> second(bytes.begin() + 6, bytes.end());
1551 EXPECT_TRUE(
1552 mockRead.deliver(std::make_unique<MessageData>(std::move(first))));
1553 EXPECT_TRUE(
1554 mockRead.deliver(std::make_unique<MessageData>(std::move(second))));
1555 };
1556 deliverCoord(/*y=*/11, /*x=*/22); // call 1's response
1557 deliverCoord(/*y=*/33, /*x=*/44); // call 2's response
1558
1559 // Intentionally retrieve the second call's result first.
1560 FragmentedCoordBatch r2 = f2.get();
1561 ASSERT_EQ(r2.coords.size(), 1u);
1562 EXPECT_EQ(r2.coords[0].y, 33u);
1563 EXPECT_EQ(r2.coords[0].x, 44u);
1564
1565 FragmentedCoordBatch r1 = f1.get();
1566 ASSERT_EQ(r1.coords.size(), 1u);
1567 EXPECT_EQ(r1.coords[0].y, 11u);
1568 EXPECT_EQ(r1.coords[0].x, 22u);
1569
1570 delete func;
1571}
1572
1573} // namespace
Arrays have a compile time specified (static) size and an element type.
Definition Types.h:301
Bits are just an array of bits.
Definition Types.h:224
Bundles represent a collection of channels.
Definition Types.h:104
std::vector< std::tuple< std::string, Direction, const Type * > > ChannelVector
Definition Types.h:108
Channels are the basic communication primitives.
Definition Types.h:125
A concrete flat message backed by a single vector of bytes.
Definition Common.h:197
const std::vector< uint8_t > & getData() const
Get the data as a vector of bytes.
Definition Common.h:211
const uint8_t * getBytes() const
Definition Common.h:208
const T * as() const
Cast to a type.
Definition Common.h:232
size_t getSize() const
Get the size of the data in bytes.
Definition Common.h:222
static MessageData from(T &t)
Cast from a type to its raw bytes.
Definition Common.h:242
detail::TypedReadOwnedCallback< DeserializerWithESIID > OutputCallback
Definition TypedPorts.h:241
std::vector< std::unique_ptr< DeserializerWithESIID > > DecodedOutputs
Definition TypedPorts.h:242
A ChannelPort which reads data from the accelerator.
Definition Ports.h:479
Signed integer.
Definition Types.h:242
Abstract multi-segment message.
Definition Common.h:151
Structs are an ordered collection of fields, each with a name and a type.
Definition Types.h:264
Type aliases provide a named type which forwards to an inner type.
Definition Types.h:166
Root class of the ESI type system.
Definition Types.h:36
Strongly typed wrapper around a raw read channel.
Definition TypedPorts.h:733
Unsigned integer.
Definition Types.h:253
The "void" type is a special type which can be used to represent no type.
Definition Types.h:141
Windows represent a fixed-size sliding window over a stream of data.
Definition Types.h:334
A ChannelPort which sends data to the accelerator.
Definition Ports.h:308
static Function * get(AppID id, BundleType *type, WriteChannelPort &arg, ReadChannelPort &result)
Definition Services.cpp:308
connect(destination, source)
Definition support.py:39
Definition esi.py:1
void verifyTypeCompatibility(const Type *portType)
Definition TypedPorts.h:456
WireInfo getWireInfo(const Type *portType)
Definition TypedPorts.h:70
const T * getUnwrappedAs(const Type *type)
Unwrap aliases and channels, then cast the payload to T.
Definition Types.h:203
T fromMessageData(const MessageData &msg, WireInfo wi)
Unpack a MessageData into a C++ integral value with the given wire info.
Definition TypedPorts.h:103
const Type * unwrapTypeAlias(const Type *type)
Unwrap TypeAliasType (possibly recursively) to get the underlying type.
Definition Types.h:193
read(addr)
Definition xrt_cosim.py:23
Compute the wire byte count for a port type.
Definition TypedPorts.h:65
size_t bitWidth
Definition TypedPorts.h:67