CIRCT 24.0.0git
Loading...
Searching...
No Matches
ESIRuntimeSegmentTest.cpp
Go to the documentation of this file.
1//===- ESIRuntimeSegmentTest.cpp - Segment HostMem region 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/Common.h"
10#include "esi/Services.h"
11#include "gtest/gtest.h"
12#include <cstdint>
13#include <limits>
14#include <memory>
15#include <stdexcept>
16#include <type_traits>
17#include <vector>
18
19using namespace esi;
22
23namespace {
24
25static_assert(std::is_same_v<HostMem::HostMemRegion, HostMemRegion>);
26static_assert(std::is_base_of_v<services::HostMemAllocator, HostMem>);
27static_assert(std::is_aggregate_v<Segment>);
28
29/// A region whose host and device addresses are arbitrary integers. Never
30/// dereferenced.
31struct FakeRegion : public HostMemRegion {
32 FakeRegion(uintptr_t host, uintptr_t dev, std::size_t size)
33 : host(host), dev(dev), size(size) {}
34 void *getPtr() const override { return reinterpret_cast<void *>(host); }
35 void *getDevicePtr() const override { return reinterpret_cast<void *>(dev); }
36 std::size_t getSize() const override { return size; }
37 uintptr_t host, dev;
38 std::size_t size;
39};
40
41/// A region backed by a real buffer. Sets `*destroyed` when deconstructed.
42struct BufferRegion : public HostMemRegion {
43 BufferRegion(std::size_t size, uintptr_t dev, bool *destroyed = nullptr)
44 : buf(size), dev(dev), destroyed(destroyed) {}
45 ~BufferRegion() override {
46 if (destroyed)
47 *destroyed = true;
48 }
49 void *getPtr() const override { return const_cast<uint8_t *>(buf.data()); }
50 void *getDevicePtr() const override { return reinterpret_cast<void *>(dev); }
51 std::size_t getSize() const override { return buf.size(); }
52 std::vector<uint8_t> buf;
53 uintptr_t dev;
54 bool *destroyed;
55};
56
57static const void *at(uintptr_t addr) {
58 return reinterpret_cast<const void *>(addr);
59}
60
61TEST(HostMemRegionTest, GetDeviceAddress) {
62 FakeRegion r(0x1000, 0xA000'0000, 0x100);
63 EXPECT_EQ(r.getDeviceAddress(at(0x1000), 0x100), 0xA000'0000u);
64 EXPECT_EQ(r.getDeviceAddress(at(0x10FF), 1), 0xA000'00FFu);
65 // Empty, starting before, ending past, starting past, and wrapping ranges.
66 EXPECT_FALSE(r.getDeviceAddress(at(0x1000), 0));
67 EXPECT_FALSE(r.getDeviceAddress(at(0x0FFF), 2));
68 EXPECT_FALSE(r.getDeviceAddress(at(0x10FF), 2));
69 EXPECT_FALSE(r.getDeviceAddress(at(0x1100), 1));
70 EXPECT_FALSE(r.getDeviceAddress(at(0x1080), SIZE_MAX));
71
72 constexpr uintptr_t maxPtr = std::numeric_limits<uintptr_t>::max();
73 // Host range at the top of the address space.
74 FakeRegion top(maxPtr - 0xF, 0x2000, 0x10);
75 EXPECT_EQ(top.getDeviceAddress(at(maxPtr - 0x7), 8), 0x2008u);
76 EXPECT_FALSE(top.getDeviceAddress(at(maxPtr - 0x7), 9));
77
78 // Device range at the top of the address space.
79 if constexpr (sizeof(uintptr_t) == sizeof(uint64_t)) {
80 FakeRegion devTop(0x1000, maxPtr - 0x3, 0x10);
81 EXPECT_EQ(devTop.getDeviceAddress(at(0x1000), 4), maxPtr - 0x3);
82 EXPECT_FALSE(devTop.getDeviceAddress(at(0x1000), 5));
83 EXPECT_FALSE(devTop.getDeviceAddress(at(0x1004), 1));
84 }
85}
86
87TEST(SegmentTest, DeviceAddress) {
88 uint8_t bytes[4] = {};
89 EXPECT_FALSE((Segment{bytes, sizeof(bytes)}.getDeviceAddress()));
90
91 BufferRegion region(16, 0x8000'0000);
92 const uint8_t *data = region.buf.data();
93 EXPECT_EQ((Segment{data + 8, 4, &region}.getDeviceAddress()), 0x8000'0008u);
94 EXPECT_FALSE((Segment{data + 14, 4, &region}.getDeviceAddress()));
95}
96
97/// A header with no region, then two 4-byte segments sharing one region which
98/// the message owns and hands over via take() after both have been taken.
99struct TestMessage : public SegmentedMessageData {
100 TestMessage(bool *destroyed = nullptr)
101 : header{0xA0, 0xA1},
102 region(std::make_unique<BufferRegion>(8, 0xD000'0000, destroyed)) {
103 base = static_cast<const uint8_t *>(region->getPtr());
104 }
105
106 size_t numSegments() const override { return 3; }
107 Segment segment(size_t idx) const override {
108 if (taken.at(idx))
109 throw std::runtime_error("segment has been taken");
110 if (idx == 0)
111 return {header.data(), header.size()};
112 return {base + (idx - 1) * 4, 4, region.get()};
113 }
114 std::unique_ptr<HostMemRegion> take(size_t idx) override {
115 if (idx == 0)
116 return nullptr;
117 if (taken.at(idx))
118 throw std::runtime_error("segment has been taken");
119 taken[idx] = true;
120 return taken[1] && taken[2] ? std::move(region) : nullptr;
121 }
122
123 std::vector<uint8_t> header;
124 std::unique_ptr<HostMemRegion> region;
125 const uint8_t *base;
126 std::vector<bool> taken = {false, false, false};
127};
128
129TEST(SegmentTest, CursorRemainingSegmentKeepsRegion) {
130 TestMessage msg;
131 SegmentedMessageDataCursor cursor(msg);
132 EXPECT_EQ(cursor.remainingSegment().region, nullptr);
133
134 // A partial advance keeps the region; the device address follows the data.
135 cursor.advance(3);
136 Segment s = cursor.remainingSegment();
137 EXPECT_EQ(s.data, msg.base + 1);
138 EXPECT_EQ(s.size, 3u);
139 EXPECT_EQ(s.region, msg.region.get());
140 EXPECT_EQ(s.getDeviceAddress(), 0xD000'0001u);
141 EXPECT_EQ(cursor.remaining().data(), s.data);
142
143 cursor.advance(3);
144 EXPECT_EQ(cursor.remainingSegment().getDeviceAddress(), 0xD000'0004u);
145 cursor.advance(4);
146 EXPECT_TRUE(cursor.done());
147 EXPECT_EQ(cursor.remainingSegment().data, nullptr);
148}
149
150TEST(SegmentTest, TakeHandsOverSharedRegionAfterLastUser) {
151 bool destroyed = false;
152 auto msg = std::make_unique<TestMessage>(&destroyed);
153 HostMemRegion *shared = msg->region.get();
154
155 // Segments without a region are unaffected by take().
156 EXPECT_EQ(msg->take(0), nullptr);
157 EXPECT_EQ(msg->segment(0).size, 2u);
158
159 // Taking the first user marks it taken but keeps the shared region.
160 EXPECT_EQ(msg->take(1), nullptr);
161 EXPECT_THROW(msg->segment(1), std::runtime_error);
162 EXPECT_THROW(msg->take(1), std::runtime_error);
163 EXPECT_THROW(msg->toMessageData(), std::runtime_error);
164 EXPECT_EQ(msg->segment(2).region, shared);
165
166 // A cursor created after a take() throws upon reaching the taken segment.
167 SegmentedMessageDataCursor cursor(*msg);
168 EXPECT_THROW(cursor.advance(3), std::runtime_error);
169
170 // Taking the last user hands over the region, which outlives the message.
171 std::unique_ptr<HostMemRegion> region = msg->take(2);
172 EXPECT_EQ(region.get(), shared);
173 msg.reset();
174 EXPECT_FALSE(destroyed);
175 region.reset();
176 EXPECT_TRUE(destroyed);
177
178 // An untaken region is freed with its message.
179 destroyed = false;
180 { TestMessage untaken(&destroyed); }
181 EXPECT_TRUE(destroyed);
182}
183
184TEST(SegmentTest, MessageDataHasNoRegions) {
185 MessageData md(std::vector<uint8_t>{1, 2, 3});
186 EXPECT_EQ(md.segment(0).region, nullptr);
187 EXPECT_EQ(md.take(0), nullptr);
188 EXPECT_EQ(md.segment(0).data, md.getBytes());
189}
190
191} // namespace
A concrete flat message backed by a single vector of bytes.
Definition Common.h:197
Cursor for incremental consumption of a SegmentedMessageData.
Definition Common.h:273
Abstract multi-segment message.
Definition Common.h:151
Definition esi.py:1
A contiguous, non-owning view of bytes within a SegmentedMessageData.
Definition Common.h:122
std::optional< uint64_t > getDeviceAddress() const
Device address of this segment's bytes, if region is set and contains them; std::nullopt otherwise.
Definition Common.cpp:80
RAII memory region for host memory in accelerator-accessible address space.
Definition Services.h:237