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(Buffer, P, 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(NextRecord, reinterpret_cast<const char *>(&R), 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(memory_order_release);
88    atomic_fetch_add(Buffer.Extents, sizeof(T), 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(NextRecord, reinterpret_cast<const char *>(Recs), 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(memory_order_release);
118    atomic_fetch_add(Buffer.Extents, Size, 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>(Arg);
152    NextRecord = reinterpret_cast<char *>(internal_memcpy(
153                     NextRecord, reinterpret_cast<char *>(&R), sizeof(R))) +
154                 sizeof(R);
155    NextRecord = reinterpret_cast<char *>(internal_memcpy(
156                     NextRecord, reinterpret_cast<char *>(&A), 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(memory_order_release);
162    atomic_fetch_add(Buffer.Extents, sizeof(R) + sizeof(A),
163                     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            EventSize, Delta);
176    NextRecord = reinterpret_cast<char *>(internal_memcpy(
177                     NextRecord, reinterpret_cast<char *>(&R), sizeof(R))) +
178                 sizeof(R);
179    NextRecord = reinterpret_cast<char *>(
180                     internal_memcpy(NextRecord, Event, 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(memory_order_release);
187    atomic_fetch_add(Buffer.Extents, sizeof(R) + EventSize,
188                     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            EventSize, Delta, EventType);
199    NextRecord = reinterpret_cast<char *>(internal_memcpy(
200                     NextRecord, reinterpret_cast<char *>(&R), sizeof(R))) +
201                 sizeof(R);
202    NextRecord = reinterpret_cast<char *>(
203                     internal_memcpy(NextRecord, Event, 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(memory_order_release);
210    atomic_fetch_add(Buffer.Extents, EventSize, 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(Buffer.Extents, 0, 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(Buffer.Extents, B, 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