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