// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_DIAGNOSTICS_SYSTEM_JIT_METADATA_WIN_H_
#define V8_DIAGNOSTICS_SYSTEM_JIT_METADATA_WIN_H_

#include <Windows.h>
#include <TraceLoggingProvider.h>
#include <evntprov.h>
#include <evntrace.h>  // defines TRACE_LEVEL_* and EVENT_TRACE_TYPE_*

#include <cstdint>
#include <string>
#include <unordered_set>
#include <utility>

namespace v8 {
namespace internal {
namespace ETWJITInterface {

/*******************************************************************************
Helper templates to create tightly packed metadata of the format expected by the
ETW data structures.
*******************************************************************************/

// All "manifest-free" events should go to channel 11 by default
const uint8_t kManifestFreeChannel = 11;

// Number of metadescriptors. Use this to find out the index of the field
// descriptors in the descriptors_array
const uint8_t kMetaDescriptorsCount = 2;

// Filtering keyword to find JScript stack-walking events
constexpr uint64_t kJScriptRuntimeKeyword = 1;

constexpr uint16_t kSourceLoadEventID = 41;
constexpr uint16_t kMethodLoadEventID = 9;

// Structure to treat a string literal, or char[], as a constexpr byte sequence
template <size_t count>
struct str_bytes {
  template <std::size_t... idx>
  constexpr str_bytes(char const (&str)[count], std::index_sequence<idx...>)
      : bytes{str[idx]...}, size(count) {}

  // Concatenate two str_bytes
  template <std::size_t count1, std::size_t count2, std::size_t... idx1,
            std::size_t... idx2>
  constexpr str_bytes(const str_bytes<count1>& s1, std::index_sequence<idx1...>,
                      const str_bytes<count2>& s2, std::index_sequence<idx2...>)
      : bytes{s1.bytes[idx1]..., s2.bytes[idx2]...}, size(count) {}

  char bytes[count];  // NOLINT
  size_t size;
};

// Specialization for 0 (base case when joining fields)
template <>
struct str_bytes<0> {
  constexpr str_bytes() : bytes{}, size(0) {}
  char bytes[1];  // MSVC doesn't like an array of 0 bytes
  size_t size;
};

// Factory function to simplify creating a str_bytes from a string literal
template <size_t count, typename idx = std::make_index_sequence<count>>
constexpr auto MakeStrBytes(char const (&s)[count]) {
  return str_bytes<count>{s, idx{}};
}

// Concatenates two str_bytes into one
template <std::size_t size1, std::size_t size2>
constexpr auto JoinBytes(const str_bytes<size1>& str1,
                         const str_bytes<size2>& str2) {
  auto idx1 = std::make_index_sequence<size1>();
  auto idx2 = std::make_index_sequence<size2>();
  return str_bytes<size1 + size2>{str1, idx1, str2, idx2};
}

// Creates an str_bytes which is the field name suffixed with the field type
template <size_t count>
constexpr auto Field(char const (&s)[count], uint8_t type) {
  auto field_name = MakeStrBytes(s);
  const char type_arr[1] = {static_cast<char>(type)};
  return JoinBytes(field_name, MakeStrBytes(type_arr));
}

// Creates the ETW event metadata header, which consists of a uint16_t
// representing the total size, and a tag byte (always 0x00 currently).
constexpr auto Header(size_t size) {
  size_t total_size = size + 3;  // total_size includes the 2 byte size + tag
  const char header_bytes[3] = {static_cast<char>(total_size & 0xFF),
                                static_cast<char>(total_size >> 8 & 0xFF),
                                '\0'};
  return MakeStrBytes(header_bytes);
}

// The JoinFields implementations below are a set of overloads for constructing
// a str_bytes representing the concatenated fields from a parameter pack.

// Empty case needed for events with no fields.
constexpr auto JoinFields() { return str_bytes<0>{}; }

// Only one field, or base case when multiple fields.
template <typename T1>
constexpr auto JoinFields(T1 field) {
  return field;
}

// Join two or more fields together.
template <typename T1, typename T2, typename... Ts>
constexpr auto JoinFields(T1 field1, T2 field2, Ts... args) {
  auto bytes = JoinBytes(field1, field2);
  return JoinFields(bytes, args...);
}

// Creates a constexpr char[] representing the fields for an ETW event.
// Declare the variable as `constexpr static auto` and provide the event name,
// followed by a series of `Field` invocations for each field.
//
// Example:
//  constexpr static auto event_fields = EventFields("my1stEvent",
//      Field("MyIntVal", kTypeInt32),
//      Field("MyMsg", kTypeAnsiStr),
//      Field("Address", kTypePointer));
template <std::size_t count, typename... Ts>
constexpr auto EventFields(char const (&name)[count], Ts... field_args) {
  auto name_bytes = MakeStrBytes(name);
  auto fields = JoinFields(field_args...);
  auto data = JoinBytes(name_bytes, fields);

  auto header = Header(data.size);
  return JoinBytes(header, data);
}

constexpr auto EventMetadata(uint16_t id, uint64_t keywords) {
  return EVENT_DESCRIPTOR{id,
                          0,  // Version
                          kManifestFreeChannel,
                          TRACE_LEVEL_INFORMATION,  // Level
                          EVENT_TRACE_TYPE_START,   // Opcode
                          0,                        // Task
                          keywords};
}

void SetMetaDescriptors(EVENT_DATA_DESCRIPTOR* data_descriptor,
                        UINT16 const UNALIGNED* traits, const void* metadata,
                        size_t size) {
  // The first descriptor is the provider traits (just the name currently)
  uint16_t traits_size = *reinterpret_cast<const uint16_t*>(traits);
  EventDataDescCreate(data_descriptor, traits, traits_size);
  data_descriptor->Type = EVENT_DATA_DESCRIPTOR_TYPE_PROVIDER_METADATA;
  ++data_descriptor;

  // The second descriptor contains the data to describe the field layout
  EventDataDescCreate(data_descriptor, metadata, static_cast<ULONG>(size));
  data_descriptor->Type = EVENT_DATA_DESCRIPTOR_TYPE_EVENT_METADATA;
}

// Base case, no fields left to set
inline void SetFieldDescriptors(EVENT_DATA_DESCRIPTOR* data_descriptors) {}

// Need to declare all the base overloads in advance, as ther bodies may become
// a point of reference for any of the overloads, and only overloads that have
// been seen will be candidates.
template <typename... Ts>
void SetFieldDescriptors(EVENT_DATA_DESCRIPTOR* data_descriptors,
                         const std::wstring& value, const Ts&... rest);
template <typename... Ts>
void SetFieldDescriptors(EVENT_DATA_DESCRIPTOR* data_descriptors,
                         const std::string& value, const Ts&... rest);
template <typename... Ts>
void SetFieldDescriptors(EVENT_DATA_DESCRIPTOR* data_descriptors,
                         const char* value, const Ts&... rest);

// One or more fields to set
template <typename T, typename... Ts>
void SetFieldDescriptors(EVENT_DATA_DESCRIPTOR* data_descriptors,
                         const T& value, const Ts&... rest) {
  EventDataDescCreate(data_descriptors, &value, sizeof(value));
  SetFieldDescriptors(++data_descriptors, rest...);
}

// Specialize for strings
template <typename... Ts>
void SetFieldDescriptors(EVENT_DATA_DESCRIPTOR* data_descriptors,
                         const std::wstring& value, const Ts&... rest) {
  EventDataDescCreate(data_descriptors, value.data(),
                      static_cast<ULONG>(value.size() * 2 + 2));
  SetFieldDescriptors(++data_descriptors, rest...);
}

template <typename... Ts>
void SetFieldDescriptors(EVENT_DATA_DESCRIPTOR* data_descriptors,
                         const std::string& value, const Ts&... rest) {
  EventDataDescCreate(data_descriptors, value.data(),
                      static_cast<ULONG>(value.size() + 1));
  SetFieldDescriptors(++data_descriptors, rest...);
}

template <typename... Ts>
void SetFieldDescriptors(EVENT_DATA_DESCRIPTOR* data_descriptors,
                         const char* value, const Ts&... rest) {
  ULONG size = static_cast<ULONG>(strlen(value) + 1);
  EventDataDescCreate(data_descriptors, value, size);
  SetFieldDescriptors(++data_descriptors, rest...);
}

// This function does the actual writing of the event via the Win32 API
inline ULONG LogEvent(uint64_t regHandle,
                      const EVENT_DESCRIPTOR* event_descriptor,
                      EVENT_DATA_DESCRIPTOR* data_descriptor,
                      ULONG desc_count) {
  if (regHandle == 0) return ERROR_SUCCESS;
  return EventWriteTransfer(regHandle, event_descriptor, NULL /* ActivityId */,
                            NULL /* RelatedActivityId */, desc_count,
                            data_descriptor);
}

// This template is called by the provider implementation
template <typename T, typename... Fs>
void LogEventData(const TraceLoggingHProvider provider,
                  const EVENT_DESCRIPTOR* event_descriptor, T* meta,
                  const Fs&... fields) {
  const size_t descriptor_count = sizeof...(fields) + kMetaDescriptorsCount;
  EVENT_DATA_DESCRIPTOR descriptors[sizeof...(fields) + kMetaDescriptorsCount];

  SetMetaDescriptors(descriptors, provider->ProviderMetadataPtr, meta->bytes,
                     meta->size);

  EVENT_DATA_DESCRIPTOR* data_descriptors = descriptors + kMetaDescriptorsCount;
  SetFieldDescriptors(data_descriptors, fields...);

  LogEvent(provider->RegHandle, event_descriptor, descriptors,
           descriptor_count);
}

}  // namespace ETWJITInterface
}  // namespace internal
}  // namespace v8

#endif  // V8_DIAGNOSTICS_SYSTEM_JIT_METADATA_WIN_H_
