1//===-- xray_fdr_log_writer.h ---------------------------------------------===//
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// This file is a part of XRay, a function call tracing system.
10//
11//===----------------------------------------------------------------------===//
12#ifndef COMPILER_RT_LIB_XRAY_XRAY_FDR_LOG_WRITER_H_
13#define COMPILER_RT_LIB_XRAY_XRAY_FDR_LOG_WRITER_H_
14
15#include "xray_buffer_queue.h"
16#include "xray_fdr_log_records.h"
17#include <functional>
18#include <tuple>
19#include <type_traits>
20#include <utility>
21
22namespace __xray {
23
24template <size_t Index> struct SerializerImpl {
25 template <class Tuple,
26 typename std::enable_if<
27 Index<std::tuple_size<
28 typename std::remove_reference<Tuple>::type>::value,
29 int>::type = 0> static void serializeTo(char *Buffer,
30 Tuple &&T) {
31 auto P = reinterpret_cast<const char *>(&std::get<Index>(T));
32 constexpr auto Size = sizeof(std::get<Index>(T));
33 internal_memcpy(dest: Buffer, src: P, n: Size);
34 SerializerImpl<Index + 1>::serializeTo(Buffer + Size,
35 std::forward<Tuple>(T));
36 }
37
38 template <class Tuple,
39 typename std::enable_if<
40 Index >= std::tuple_size<typename std::remove_reference<
41 Tuple>::type>::value,
42 int>::type = 0>
43 static void serializeTo(char *, Tuple &&) {}
44};
45
46using Serializer = SerializerImpl<0>;
47
48template <class Tuple, size_t Index> struct AggregateSizesImpl {
49 static constexpr size_t value =
50 sizeof(typename std::tuple_element<Index, Tuple>::type) +
51 AggregateSizesImpl<Tuple, Index - 1>::value;
52};
53
54template <class Tuple> struct AggregateSizesImpl<Tuple, 0> {
55 static constexpr size_t value =
56 sizeof(typename std::tuple_element<0, Tuple>::type);
57};
58
59template <class Tuple> struct AggregateSizes {
60 static constexpr size_t value =
61 AggregateSizesImpl<Tuple, std::tuple_size<Tuple>::value - 1>::value;
62};
63
64template <MetadataRecord::RecordKinds Kind, class... DataTypes>
65MetadataRecord createMetadataRecord(DataTypes &&... Ds) {
66 static_assert(AggregateSizes<std::tuple<DataTypes...>>::value <=
67 sizeof(MetadataRecord) - 1,
68 "Metadata payload longer than metadata buffer!");
69 MetadataRecord R;
70 R.Type = 1;
71 R.RecordKind = static_cast<uint8_t>(Kind);
72 Serializer::serializeTo(R.Data,
73 std::make_tuple(std::forward<DataTypes>(Ds)...));
74 return R;
75}
76
77class FDRLogWriter {
78 BufferQueue::Buffer &Buffer;
79 char *NextRecord = nullptr;
80
81 template <class T> void writeRecord(const T &R) {
82 internal_memcpy(dest: NextRecord, src: reinterpret_cast<const char *>(&R), n: sizeof(T));
83 NextRecord += sizeof(T);
84 // We need this atomic fence here to ensure that other threads attempting to
85 // read the bytes in the buffer will see the writes committed before the
86 // extents are updated.
87 atomic_thread_fence(mo: memory_order_release);
88 atomic_fetch_add(a: Buffer.Extents, v: sizeof(T), mo: memory_order_acq_rel);
89 }
90
91public:
92 explicit FDRLogWriter(BufferQueue::Buffer &B, char *P)
93 : Buffer(B), NextRecord(P) {
94 DCHECK_NE(Buffer.Data, nullptr);
95 DCHECK_NE(NextRecord, nullptr);
96 }
97
98 explicit FDRLogWriter(BufferQueue::Buffer &B)
99 : FDRLogWriter(B, static_cast<char *>(B.Data)) {}
100
101 template <MetadataRecord::RecordKinds Kind, class... Data>
102 bool writeMetadata(Data &&... Ds) {
103 // TODO: Check boundary conditions:
104 // 1) Buffer is full, and cannot handle one metadata record.
105 // 2) Buffer queue is finalising.
106 writeRecord(createMetadataRecord<Kind>(std::forward<Data>(Ds)...));
107 return true;
108 }
109
110 template <size_t N> size_t writeMetadataRecords(MetadataRecord (&Recs)[N]) {
111 constexpr auto Size = sizeof(MetadataRecord) * N;
112 internal_memcpy(dest: NextRecord, src: reinterpret_cast<const char *>(Recs), n: Size);
113 NextRecord += Size;
114 // We need this atomic fence here to ensure that other threads attempting to
115 // read the bytes in the buffer will see the writes committed before the
116 // extents are updated.
117 atomic_thread_fence(mo: memory_order_release);
118 atomic_fetch_add(a: Buffer.Extents, v: Size, mo: memory_order_acq_rel);
119 return Size;
120 }
121
122 enum class FunctionRecordKind : uint8_t {
123 Enter = 0x00,
124 Exit = 0x01,
125 TailExit = 0x02,
126 EnterArg = 0x03,
127 };
128
129 bool writeFunction(FunctionRecordKind Kind, int32_t FuncId, int32_t Delta) {
130 FunctionRecord R;
131 R.Type = 0;
132 R.RecordKind = uint8_t(Kind);
133 R.FuncId = FuncId;
134 R.TSCDelta = Delta;
135 writeRecord(R);
136 return true;
137 }
138
139 bool writeFunctionWithArg(FunctionRecordKind Kind, int32_t FuncId,
140 int32_t Delta, uint64_t Arg) {
141 // We need to write the function with arg into the buffer, and then
142 // atomically update the buffer extents. This ensures that any reads
143 // synchronised on the buffer extents record will always see the writes
144 // that happen before the atomic update.
145 FunctionRecord R;
146 R.Type = 0;
147 R.RecordKind = uint8_t(Kind);
148 R.FuncId = FuncId;
149 R.TSCDelta = Delta;
150 MetadataRecord A =
151 createMetadataRecord<MetadataRecord::RecordKinds::CallArgument>(Ds&: Arg);
152 NextRecord = reinterpret_cast<char *>(internal_memcpy(
153 dest: NextRecord, src: reinterpret_cast<char *>(&R), n: sizeof(R))) +
154 sizeof(R);
155 NextRecord = reinterpret_cast<char *>(internal_memcpy(
156 dest: NextRecord, src: reinterpret_cast<char *>(&A), n: sizeof(A))) +
157 sizeof(A);
158 // We need this atomic fence here to ensure that other threads attempting to
159 // read the bytes in the buffer will see the writes committed before the
160 // extents are updated.
161 atomic_thread_fence(mo: memory_order_release);
162 atomic_fetch_add(a: Buffer.Extents, v: sizeof(R) + sizeof(A),
163 mo: memory_order_acq_rel);
164 return true;
165 }
166
167 bool writeCustomEvent(int32_t Delta, const void *Event, int32_t EventSize) {
168 // We write the metadata record and the custom event data into the buffer
169 // first, before we atomically update the extents for the buffer. This
170 // allows us to ensure that any threads reading the extents of the buffer
171 // will only ever see the full metadata and custom event payload accounted
172 // (no partial writes accounted).
173 MetadataRecord R =
174 createMetadataRecord<MetadataRecord::RecordKinds::CustomEventMarker>(
175 Ds&: EventSize, Ds&: Delta);
176 NextRecord = reinterpret_cast<char *>(internal_memcpy(
177 dest: NextRecord, src: reinterpret_cast<char *>(&R), n: sizeof(R))) +
178 sizeof(R);
179 NextRecord = reinterpret_cast<char *>(
180 internal_memcpy(dest: NextRecord, src: Event, n: EventSize)) +
181 EventSize;
182
183 // We need this atomic fence here to ensure that other threads attempting to
184 // read the bytes in the buffer will see the writes committed before the
185 // extents are updated.
186 atomic_thread_fence(mo: memory_order_release);
187 atomic_fetch_add(a: Buffer.Extents, v: sizeof(R) + EventSize,
188 mo: memory_order_acq_rel);
189 return true;
190 }
191
192 bool writeTypedEvent(int32_t Delta, uint16_t EventType, const void *Event,
193 int32_t EventSize) {
194 // We do something similar when writing out typed events, see
195 // writeCustomEvent(...) above for details.
196 MetadataRecord R =
197 createMetadataRecord<MetadataRecord::RecordKinds::TypedEventMarker>(
198 Ds&: EventSize, Ds&: Delta, Ds&: EventType);
199 NextRecord = reinterpret_cast<char *>(internal_memcpy(
200 dest: NextRecord, src: reinterpret_cast<char *>(&R), n: sizeof(R))) +
201 sizeof(R);
202 NextRecord = reinterpret_cast<char *>(
203 internal_memcpy(dest: NextRecord, src: Event, n: EventSize)) +
204 EventSize;
205
206 // We need this atomic fence here to ensure that other threads attempting to
207 // read the bytes in the buffer will see the writes committed before the
208 // extents are updated.
209 atomic_thread_fence(mo: memory_order_release);
210 atomic_fetch_add(a: Buffer.Extents, v: EventSize, mo: memory_order_acq_rel);
211 return true;
212 }
213
214 char *getNextRecord() const { return NextRecord; }
215
216 void resetRecord() {
217 NextRecord = reinterpret_cast<char *>(Buffer.Data);
218 atomic_store(a: Buffer.Extents, v: 0, mo: memory_order_release);
219 }
220
221 void undoWrites(size_t B) {
222 DCHECK_GE(NextRecord - B, reinterpret_cast<char *>(Buffer.Data));
223 NextRecord -= B;
224 atomic_fetch_sub(a: Buffer.Extents, v: B, mo: memory_order_acq_rel);
225 }
226
227}; // namespace __xray
228
229} // namespace __xray
230
231#endif // COMPILER-RT_LIB_XRAY_XRAY_FDR_LOG_WRITER_H_
232