//===-- xray_fdr_log_writer.h ---------------------------------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // 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 #include #include #include namespace __xray { template struct SerializerImpl { template ::type>::value, int>::type = 0> static void serializeTo(char *Buffer, Tuple &&T) { auto P = reinterpret_cast(&std::get(T)); constexpr auto Size = sizeof(std::get(T)); internal_memcpy(Buffer, P, Size); SerializerImpl::serializeTo(Buffer + Size, std::forward(T)); } template = std::tuple_size::type>::value, int>::type = 0> static void serializeTo(char *, Tuple &&) {} }; using Serializer = SerializerImpl<0>; template struct AggregateSizesImpl { static constexpr size_t value = sizeof(typename std::tuple_element::type) + AggregateSizesImpl::value; }; template struct AggregateSizesImpl { static constexpr size_t value = sizeof(typename std::tuple_element<0, Tuple>::type); }; template struct AggregateSizes { static constexpr size_t value = AggregateSizesImpl::value - 1>::value; }; template MetadataRecord createMetadataRecord(DataTypes &&... Ds) { static_assert(AggregateSizes>::value <= sizeof(MetadataRecord) - 1, "Metadata payload longer than metadata buffer!"); MetadataRecord R; R.Type = 1; R.RecordKind = static_cast(Kind); Serializer::serializeTo(R.Data, std::make_tuple(std::forward(Ds)...)); return R; } class FDRLogWriter { BufferQueue::Buffer &Buffer; char *NextRecord = nullptr; template void writeRecord(const T &R) { internal_memcpy(NextRecord, reinterpret_cast(&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(B.Data)) {} template 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(std::forward(Ds)...)); return true; } template size_t writeMetadataRecords(MetadataRecord (&Recs)[N]) { constexpr auto Size = sizeof(MetadataRecord) * N; internal_memcpy(NextRecord, reinterpret_cast(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(Arg); NextRecord = reinterpret_cast(internal_memcpy( NextRecord, reinterpret_cast(&R), sizeof(R))) + sizeof(R); NextRecord = reinterpret_cast(internal_memcpy( NextRecord, reinterpret_cast(&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( EventSize, Delta); NextRecord = reinterpret_cast(internal_memcpy( NextRecord, reinterpret_cast(&R), sizeof(R))) + sizeof(R); NextRecord = reinterpret_cast( 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( EventSize, Delta, EventType); NextRecord = reinterpret_cast(internal_memcpy( NextRecord, reinterpret_cast(&R), sizeof(R))) + sizeof(R); NextRecord = reinterpret_cast( 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(Buffer.Data); atomic_store(Buffer.Extents, 0, memory_order_release); } void undoWrites(size_t B) { DCHECK_GE(NextRecord - B, reinterpret_cast(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_