|  | //===-- xray_fdr_log_writer.h ---------------------------------------------===// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  | // | 
|  | // This file is a part of XRay, a function call tracing system. | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  | #ifndef COMPILER_RT_LIB_XRAY_XRAY_FDR_LOG_WRITER_H_ | 
|  | #define COMPILER_RT_LIB_XRAY_XRAY_FDR_LOG_WRITER_H_ | 
|  |  | 
|  | #include "xray_buffer_queue.h" | 
|  | #include "xray_fdr_log_records.h" | 
|  | #include <functional> | 
|  | #include <tuple> | 
|  | #include <type_traits> | 
|  | #include <utility> | 
|  |  | 
|  | namespace __xray { | 
|  |  | 
|  | template <size_t Index> struct SerializerImpl { | 
|  | template <class Tuple, | 
|  | typename std::enable_if< | 
|  | Index<std::tuple_size< | 
|  | typename std::remove_reference<Tuple>::type>::value, | 
|  | int>::type = 0> static void serializeTo(char *Buffer, | 
|  | Tuple &&T) { | 
|  | auto P = reinterpret_cast<const char *>(&std::get<Index>(T)); | 
|  | constexpr auto Size = sizeof(std::get<Index>(T)); | 
|  | internal_memcpy(Buffer, P, Size); | 
|  | SerializerImpl<Index + 1>::serializeTo(Buffer + Size, | 
|  | std::forward<Tuple>(T)); | 
|  | } | 
|  |  | 
|  | template <class Tuple, | 
|  | typename std::enable_if< | 
|  | Index >= std::tuple_size<typename std::remove_reference< | 
|  | Tuple>::type>::value, | 
|  | int>::type = 0> | 
|  | static void serializeTo(char *, Tuple &&) {} | 
|  | }; | 
|  |  | 
|  | using Serializer = SerializerImpl<0>; | 
|  |  | 
|  | template <class Tuple, size_t Index> struct AggregateSizesImpl { | 
|  | static constexpr size_t value = | 
|  | sizeof(typename std::tuple_element<Index, Tuple>::type) + | 
|  | AggregateSizesImpl<Tuple, Index - 1>::value; | 
|  | }; | 
|  |  | 
|  | template <class Tuple> struct AggregateSizesImpl<Tuple, 0> { | 
|  | static constexpr size_t value = | 
|  | sizeof(typename std::tuple_element<0, Tuple>::type); | 
|  | }; | 
|  |  | 
|  | template <class Tuple> struct AggregateSizes { | 
|  | static constexpr size_t value = | 
|  | AggregateSizesImpl<Tuple, std::tuple_size<Tuple>::value - 1>::value; | 
|  | }; | 
|  |  | 
|  | template <MetadataRecord::RecordKinds Kind, class... DataTypes> | 
|  | MetadataRecord createMetadataRecord(DataTypes &&... Ds) { | 
|  | static_assert(AggregateSizes<std::tuple<DataTypes...>>::value <= | 
|  | sizeof(MetadataRecord) - 1, | 
|  | "Metadata payload longer than metadata buffer!"); | 
|  | MetadataRecord R; | 
|  | R.Type = 1; | 
|  | R.RecordKind = static_cast<uint8_t>(Kind); | 
|  | Serializer::serializeTo(R.Data, | 
|  | std::make_tuple(std::forward<DataTypes>(Ds)...)); | 
|  | return R; | 
|  | } | 
|  |  | 
|  | class FDRLogWriter { | 
|  | BufferQueue::Buffer &Buffer; | 
|  | char *NextRecord = nullptr; | 
|  |  | 
|  | template <class T> void writeRecord(const T &R) { | 
|  | internal_memcpy(NextRecord, reinterpret_cast<const char *>(&R), sizeof(T)); | 
|  | NextRecord += sizeof(T); | 
|  | // We need this atomic fence here to ensure that other threads attempting to | 
|  | // read the bytes in the buffer will see the writes committed before the | 
|  | // extents are updated. | 
|  | atomic_thread_fence(memory_order_release); | 
|  | atomic_fetch_add(Buffer.Extents, sizeof(T), memory_order_acq_rel); | 
|  | } | 
|  |  | 
|  | public: | 
|  | explicit FDRLogWriter(BufferQueue::Buffer &B, char *P) | 
|  | : Buffer(B), NextRecord(P) { | 
|  | DCHECK_NE(Buffer.Data, nullptr); | 
|  | DCHECK_NE(NextRecord, nullptr); | 
|  | } | 
|  |  | 
|  | explicit FDRLogWriter(BufferQueue::Buffer &B) | 
|  | : FDRLogWriter(B, static_cast<char *>(B.Data)) {} | 
|  |  | 
|  | template <MetadataRecord::RecordKinds Kind, class... Data> | 
|  | bool writeMetadata(Data &&... Ds) { | 
|  | // TODO: Check boundary conditions: | 
|  | // 1) Buffer is full, and cannot handle one metadata record. | 
|  | // 2) Buffer queue is finalising. | 
|  | writeRecord(createMetadataRecord<Kind>(std::forward<Data>(Ds)...)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | template <size_t N> size_t writeMetadataRecords(MetadataRecord (&Recs)[N]) { | 
|  | constexpr auto Size = sizeof(MetadataRecord) * N; | 
|  | internal_memcpy(NextRecord, reinterpret_cast<const char *>(Recs), Size); | 
|  | NextRecord += Size; | 
|  | // We need this atomic fence here to ensure that other threads attempting to | 
|  | // read the bytes in the buffer will see the writes committed before the | 
|  | // extents are updated. | 
|  | atomic_thread_fence(memory_order_release); | 
|  | atomic_fetch_add(Buffer.Extents, Size, memory_order_acq_rel); | 
|  | return Size; | 
|  | } | 
|  |  | 
|  | enum class FunctionRecordKind : uint8_t { | 
|  | Enter = 0x00, | 
|  | Exit = 0x01, | 
|  | TailExit = 0x02, | 
|  | EnterArg = 0x03, | 
|  | }; | 
|  |  | 
|  | bool writeFunction(FunctionRecordKind Kind, int32_t FuncId, int32_t Delta) { | 
|  | FunctionRecord R; | 
|  | R.Type = 0; | 
|  | R.RecordKind = uint8_t(Kind); | 
|  | R.FuncId = FuncId; | 
|  | R.TSCDelta = Delta; | 
|  | writeRecord(R); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool writeFunctionWithArg(FunctionRecordKind Kind, int32_t FuncId, | 
|  | int32_t Delta, uint64_t Arg) { | 
|  | // We need to write the function with arg into the buffer, and then | 
|  | // atomically update the buffer extents. This ensures that any reads | 
|  | // synchronised on the buffer extents record will always see the writes | 
|  | // that happen before the atomic update. | 
|  | FunctionRecord R; | 
|  | R.Type = 0; | 
|  | R.RecordKind = uint8_t(Kind); | 
|  | R.FuncId = FuncId; | 
|  | R.TSCDelta = Delta; | 
|  | MetadataRecord A = | 
|  | createMetadataRecord<MetadataRecord::RecordKinds::CallArgument>(Arg); | 
|  | NextRecord = reinterpret_cast<char *>(internal_memcpy( | 
|  | NextRecord, reinterpret_cast<char *>(&R), sizeof(R))) + | 
|  | sizeof(R); | 
|  | NextRecord = reinterpret_cast<char *>(internal_memcpy( | 
|  | NextRecord, reinterpret_cast<char *>(&A), sizeof(A))) + | 
|  | sizeof(A); | 
|  | // We need this atomic fence here to ensure that other threads attempting to | 
|  | // read the bytes in the buffer will see the writes committed before the | 
|  | // extents are updated. | 
|  | atomic_thread_fence(memory_order_release); | 
|  | atomic_fetch_add(Buffer.Extents, sizeof(R) + sizeof(A), | 
|  | memory_order_acq_rel); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool writeCustomEvent(int32_t Delta, const void *Event, int32_t EventSize) { | 
|  | // We write the metadata record and the custom event data into the buffer | 
|  | // first, before we atomically update the extents for the buffer. This | 
|  | // allows us to ensure that any threads reading the extents of the buffer | 
|  | // will only ever see the full metadata and custom event payload accounted | 
|  | // (no partial writes accounted). | 
|  | MetadataRecord R = | 
|  | createMetadataRecord<MetadataRecord::RecordKinds::CustomEventMarker>( | 
|  | EventSize, Delta); | 
|  | NextRecord = reinterpret_cast<char *>(internal_memcpy( | 
|  | NextRecord, reinterpret_cast<char *>(&R), sizeof(R))) + | 
|  | sizeof(R); | 
|  | NextRecord = reinterpret_cast<char *>( | 
|  | internal_memcpy(NextRecord, Event, EventSize)) + | 
|  | EventSize; | 
|  |  | 
|  | // We need this atomic fence here to ensure that other threads attempting to | 
|  | // read the bytes in the buffer will see the writes committed before the | 
|  | // extents are updated. | 
|  | atomic_thread_fence(memory_order_release); | 
|  | atomic_fetch_add(Buffer.Extents, sizeof(R) + EventSize, | 
|  | memory_order_acq_rel); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool writeTypedEvent(int32_t Delta, uint16_t EventType, const void *Event, | 
|  | int32_t EventSize) { | 
|  | // We do something similar when writing out typed events, see | 
|  | // writeCustomEvent(...) above for details. | 
|  | MetadataRecord R = | 
|  | createMetadataRecord<MetadataRecord::RecordKinds::TypedEventMarker>( | 
|  | EventSize, Delta, EventType); | 
|  | NextRecord = reinterpret_cast<char *>(internal_memcpy( | 
|  | NextRecord, reinterpret_cast<char *>(&R), sizeof(R))) + | 
|  | sizeof(R); | 
|  | NextRecord = reinterpret_cast<char *>( | 
|  | internal_memcpy(NextRecord, Event, EventSize)) + | 
|  | EventSize; | 
|  |  | 
|  | // We need this atomic fence here to ensure that other threads attempting to | 
|  | // read the bytes in the buffer will see the writes committed before the | 
|  | // extents are updated. | 
|  | atomic_thread_fence(memory_order_release); | 
|  | atomic_fetch_add(Buffer.Extents, EventSize, memory_order_acq_rel); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | char *getNextRecord() const { return NextRecord; } | 
|  |  | 
|  | void resetRecord() { | 
|  | NextRecord = reinterpret_cast<char *>(Buffer.Data); | 
|  | atomic_store(Buffer.Extents, 0, memory_order_release); | 
|  | } | 
|  |  | 
|  | void undoWrites(size_t B) { | 
|  | DCHECK_GE(NextRecord - B, reinterpret_cast<char *>(Buffer.Data)); | 
|  | NextRecord -= B; | 
|  | atomic_fetch_sub(Buffer.Extents, B, memory_order_acq_rel); | 
|  | } | 
|  |  | 
|  | }; // namespace __xray | 
|  |  | 
|  | } // namespace __xray | 
|  |  | 
|  | #endif // COMPILER-RT_LIB_XRAY_XRAY_FDR_LOG_WRITER_H_ |